mirror of
https://github.com/bringout/oca-ocb-mail.git
synced 2026-04-22 01:22:01 +02:00
Initial commit: Mail packages
This commit is contained in:
commit
4e53507711
1948 changed files with 751201 additions and 0 deletions
51
odoo-bringout-oca-ocb-test_mass_mailing/README.md
Normal file
51
odoo-bringout-oca-ocb-test_mass_mailing/README.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Mass Mail Tests
|
||||
|
||||
This module contains tests related to mass mailing. Those
|
||||
are present in a separate module to use specific test models defined in
|
||||
test_mail.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-ocb-test_mass_mailing
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- mass_mailing
|
||||
- mass_mailing_sms
|
||||
- test_mail
|
||||
- test_mail_sms
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Mass Mail Tests
|
||||
- **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_mass_mailing`.
|
||||
|
||||
## 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
|
||||
32
odoo-bringout-oca-ocb-test_mass_mailing/doc/ARCHITECTURE.md
Normal file
32
odoo-bringout-oca-ocb-test_mass_mailing/doc/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U[Users] -->|HTTP| V[Views and QWeb Templates]
|
||||
V --> C[Controllers]
|
||||
V --> W[Wizards – Transient Models]
|
||||
C --> M[Models and ORM]
|
||||
W --> M
|
||||
M --> R[Reports]
|
||||
DX[Data XML] --> M
|
||||
S[Security – ACLs and Groups] -. enforces .-> M
|
||||
|
||||
subgraph Test_mass_mailing Module - test_mass_mailing
|
||||
direction LR
|
||||
M:::layer
|
||||
W:::layer
|
||||
C:::layer
|
||||
V:::layer
|
||||
R:::layer
|
||||
S:::layer
|
||||
DX:::layer
|
||||
end
|
||||
|
||||
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
|
||||
```
|
||||
|
||||
Notes
|
||||
- Views include tree/form/kanban templates and report templates.
|
||||
- Controllers provide website/portal routes when present.
|
||||
- Wizards are UI flows implemented with `models.TransientModel`.
|
||||
- Data XML loads data/demo records; Security defines groups and access.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for test_mass_mailing. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
- [mass_mailing](../../odoo-bringout-oca-ocb-mass_mailing)
|
||||
- [mass_mailing_sms](../../odoo-bringout-oca-ocb-mass_mailing_sms)
|
||||
- [test_mail](../../odoo-bringout-oca-ocb-test_mail)
|
||||
- [test_mail_sms](../../odoo-bringout-oca-ocb-test_mail_sms)
|
||||
4
odoo-bringout-oca-ocb-test_mass_mailing/doc/FAQ.md
Normal file
4
odoo-bringout-oca-ocb-test_mass_mailing/doc/FAQ.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# FAQ
|
||||
|
||||
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
|
||||
- Q: How to enable? A: Start server with --addon test_mass_mailing or install in UI.
|
||||
7
odoo-bringout-oca-ocb-test_mass_mailing/doc/INSTALL.md
Normal file
7
odoo-bringout-oca-ocb-test_mass_mailing/doc/INSTALL.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-ocb-test_mass_mailing"
|
||||
# or
|
||||
uv pip install odoo-bringout-oca-ocb-test_mass_mailing"
|
||||
```
|
||||
22
odoo-bringout-oca-ocb-test_mass_mailing/doc/MODELS.md
Normal file
22
odoo-bringout-oca-ocb-test_mass_mailing/doc/MODELS.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Models
|
||||
|
||||
Detected core models and extensions in test_mass_mailing.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class mailing_performance
|
||||
class mailing_performance_blacklist
|
||||
class mailing_test_blacklist
|
||||
class mailing_test_customer
|
||||
class mailing_test_optout
|
||||
class mailing_test_partner
|
||||
class mailing_test_partner_unstored
|
||||
class mailing_test_simple
|
||||
class mailing_test_utm
|
||||
class utm_test_source_mixin
|
||||
class utm_test_source_mixin_other
|
||||
```
|
||||
|
||||
Notes
|
||||
- Classes show model technical names; fields omitted for brevity.
|
||||
- Items listed under _inherit are extensions of existing models.
|
||||
6
odoo-bringout-oca-ocb-test_mass_mailing/doc/OVERVIEW.md
Normal file
6
odoo-bringout-oca-ocb-test_mass_mailing/doc/OVERVIEW.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Overview
|
||||
|
||||
Packaged Odoo addon: test_mass_mailing. Provides features documented in upstream Odoo 16 under this addon.
|
||||
|
||||
- Source: OCA/OCB 16.0, addon test_mass_mailing
|
||||
- License: LGPL-3
|
||||
3
odoo-bringout-oca-ocb-test_mass_mailing/doc/REPORTS.md
Normal file
3
odoo-bringout-oca-ocb-test_mass_mailing/doc/REPORTS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
34
odoo-bringout-oca-ocb-test_mass_mailing/doc/SECURITY.md
Normal file
34
odoo-bringout-oca-ocb-test_mass_mailing/doc/SECURITY.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Security
|
||||
|
||||
Access control and security definitions in test_mass_mailing.
|
||||
|
||||
## Access Control Lists (ACLs)
|
||||
|
||||
Model access permissions defined in:
|
||||
- **[ir.model.access.csv](../test_mass_mailing/security/ir.model.access.csv)**
|
||||
- 20 model access rules
|
||||
|
||||
## Record Rules
|
||||
|
||||
Row-level security rules defined in:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Security Layers"
|
||||
A[Users] --> B[Groups]
|
||||
B --> C[Access Control Lists]
|
||||
C --> D[Models]
|
||||
B --> E[Record Rules]
|
||||
E --> F[Individual Records]
|
||||
end
|
||||
```
|
||||
|
||||
Security files overview:
|
||||
- **[ir.model.access.csv](../test_mass_mailing/security/ir.model.access.csv)**
|
||||
- Model access permissions (CRUD rights)
|
||||
|
||||
Notes
|
||||
- Access Control Lists define which groups can access which models
|
||||
- Record Rules provide row-level security (filter records by user/group)
|
||||
- Security groups organize users and define permission sets
|
||||
- All security is enforced at the ORM level by Odoo
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Troubleshooting
|
||||
|
||||
- Ensure Python and Odoo environment matches repo guidance.
|
||||
- Check database connectivity and logs if startup fails.
|
||||
- Validate that dependent addons listed in DEPENDENCIES.md are installed.
|
||||
7
odoo-bringout-oca-ocb-test_mass_mailing/doc/USAGE.md
Normal file
7
odoo-bringout-oca-ocb-test_mass_mailing/doc/USAGE.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Usage
|
||||
|
||||
Start Odoo including this addon (from repo root):
|
||||
|
||||
```bash
|
||||
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon test_mass_mailing
|
||||
```
|
||||
3
odoo-bringout-oca-ocb-test_mass_mailing/doc/WIZARDS.md
Normal file
3
odoo-bringout-oca-ocb-test_mass_mailing/doc/WIZARDS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Wizards
|
||||
|
||||
This module does not include UI wizards.
|
||||
45
odoo-bringout-oca-ocb-test_mass_mailing/pyproject.toml
Normal file
45
odoo-bringout-oca-ocb-test_mass_mailing/pyproject.toml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_mass_mailing"
|
||||
version = "16.0.0"
|
||||
description = "Mass Mail Tests - Mass Mail Tests: feature and performance tests for mass mailing"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-mass_mailing>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mass_mailing_sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mail>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mail_sms>=16.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"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.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/bringout/0"
|
||||
repository = "https://github.com/bringout/0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["test_mass_mailing"]
|
||||
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import data
|
||||
from . import models
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
{
|
||||
'name': 'Mass Mail Tests',
|
||||
'version': '1.0',
|
||||
'category': 'Hidden',
|
||||
'sequence': 8765,
|
||||
'summary': 'Mass Mail Tests: feature and performance tests for mass mailing',
|
||||
'description': """This module contains tests related to mass mailing. Those
|
||||
are present in a separate module to use specific test models defined in
|
||||
test_mail. """,
|
||||
'depends': [
|
||||
'mass_mailing',
|
||||
'mass_mailing_sms',
|
||||
'test_mail',
|
||||
'test_mail_sms',
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mail_test_data
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
||||
To: {to}
|
||||
cc: {cc}
|
||||
Received: by mail1.openerp.com (Postfix, from userid 10002)
|
||||
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
|
||||
From: {email_from}
|
||||
Subject: {subject}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="----=_Part_4200734_24778174.1344608186754"
|
||||
Date: Fri, 10 Aug 2012 14:16:26 +0000
|
||||
Message-ID: {msg_id}
|
||||
{extra}
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
I would gladly answer to your mass mailing !
|
||||
|
||||
--
|
||||
Your Dear Customer
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>=20
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" />
|
||||
</head>=20
|
||||
<body style=3D"margin: 0; padding: 0; background: #ffffff;-webkit-text-size-adjust: 100%;">=20
|
||||
|
||||
<p>I would gladly answer to your mass mailing !</p>
|
||||
|
||||
<p>--<br/>
|
||||
Your Dear Customer
|
||||
<p>
|
||||
</body>
|
||||
</html>
|
||||
------=_Part_4200734_24778174.1344608186754--
|
||||
"""
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mailing_models
|
||||
from . import mailing_models_utm
|
||||
from . import mailing_models_cornercase
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MailingCustomer(models.Model):
|
||||
""" A model inheriting from mail.thread with a partner field, to test
|
||||
mass mailing flows involving checking partner email. """
|
||||
_description = 'Mailing with partner'
|
||||
_name = 'mailing.test.customer'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char(compute='_compute_email_from', readonly=False, store=True)
|
||||
customer_id = fields.Many2one('res.partner', 'Customer', tracking=True)
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_email_from(self):
|
||||
for mailing in self.filtered(lambda rec: not rec.email_from and rec.customer_id):
|
||||
mailing.email_from = mailing.customer_id.email
|
||||
|
||||
def _message_get_default_recipients(self):
|
||||
""" Default recipient checks for 'partner_id', here the field is named
|
||||
'customer_id'. """
|
||||
default_recipients = super()._message_get_default_recipients()
|
||||
for record in self:
|
||||
if record.customer_id:
|
||||
default_recipients[record.id] = {
|
||||
'email_cc': False,
|
||||
'email_to': False,
|
||||
'partner_ids': record.customer_id.ids,
|
||||
}
|
||||
return default_recipients
|
||||
|
||||
|
||||
class MailingSimple(models.Model):
|
||||
""" Model only inheriting from mail.thread to test base mailing features and
|
||||
performances. """
|
||||
_description = 'Simple Mailing'
|
||||
_name = 'mailing.test.simple'
|
||||
_inherit = ['mail.thread']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
|
||||
|
||||
class MailingUTM(models.Model):
|
||||
""" Model inheriting from mail.thread and utm.mixin for checking utm of mailing
|
||||
is caught and set on reply """
|
||||
_description = 'Mailing: UTM enabled to test UTM sync with mailing'
|
||||
_name = 'mailing.test.utm'
|
||||
_inherit = ['mail.thread', 'utm.mixin']
|
||||
|
||||
name = fields.Char()
|
||||
|
||||
|
||||
class MailingBLacklist(models.Model):
|
||||
""" Model using blacklist mechanism for mass mailing features. """
|
||||
_description = 'Mailing Blacklist Enabled'
|
||||
_name = 'mailing.test.blacklist'
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
customer_id = fields.Many2one('res.partner', 'Customer', tracking=True)
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=True)
|
||||
|
||||
def _message_get_default_recipients(self):
|
||||
""" Default recipient checks for 'partner_id', here the field is named
|
||||
'customer_id'. """
|
||||
default_recipients = super()._message_get_default_recipients()
|
||||
for record in self:
|
||||
if record.customer_id:
|
||||
default_recipients[record.id] = {
|
||||
'email_cc': False,
|
||||
'email_to': False,
|
||||
'partner_ids': record.customer_id.ids,
|
||||
}
|
||||
return default_recipients
|
||||
|
||||
|
||||
class MailingOptOut(models.Model):
|
||||
""" Model using blacklist mechanism and a hijacked opt-out mechanism for
|
||||
mass mailing features. """
|
||||
_description = 'Mailing Blacklist / Optout Enabled'
|
||||
_name = 'mailing.test.optout'
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
opt_out = fields.Boolean()
|
||||
customer_id = fields.Many2one('res.partner', 'Customer', tracking=True)
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=True)
|
||||
|
||||
def _mailing_get_opt_out_list(self, mailing):
|
||||
res_ids = mailing._get_recipients()
|
||||
opt_out_contacts = set(self.search([
|
||||
('id', 'in', res_ids),
|
||||
('opt_out', '=', True)
|
||||
]).mapped('email_normalized'))
|
||||
return opt_out_contacts
|
||||
|
||||
def _message_get_default_recipients(self):
|
||||
""" Default recipient checks for 'partner_id', here the field is named
|
||||
'customer_id'. """
|
||||
default_recipients = super()._message_get_default_recipients()
|
||||
for record in self:
|
||||
if record.customer_id:
|
||||
default_recipients[record.id] = {
|
||||
'email_cc': False,
|
||||
'email_to': False,
|
||||
'partner_ids': record.customer_id.ids,
|
||||
}
|
||||
return default_recipients
|
||||
|
||||
class MailingTestPartner(models.Model):
|
||||
_description = 'Mailing Model with partner_id'
|
||||
_name = 'mailing.test.partner'
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
partner_id = fields.Many2one('res.partner', 'Customer')
|
||||
|
||||
|
||||
class MailingPerformance(models.Model):
|
||||
""" A very simple model only inheriting from mail.thread to test pure mass
|
||||
mailing performances. """
|
||||
_name = 'mailing.performance'
|
||||
_description = 'Mailing: base performance'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
|
||||
|
||||
class MailingPerformanceBL(models.Model):
|
||||
""" Model using blacklist mechanism for mass mailing performance. """
|
||||
_name = 'mailing.performance.blacklist'
|
||||
_description = 'Mailing: blacklist performance'
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
_primary_email = 'email_from' # blacklist field to check
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
user_id = fields.Many2one(
|
||||
'res.users', 'Responsible',
|
||||
tracking=True)
|
||||
container_id = fields.Many2one(
|
||||
'mail.test.container', 'Meta Container Record',
|
||||
tracking=True)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MailingTestPartnerUnstored(models.Model):
|
||||
""" Check mailing with unstored fields """
|
||||
_description = 'Mailing Model without stored partner_id'
|
||||
_name = 'mailing.test.partner.unstored'
|
||||
_inherit = ['mail.thread.blacklist']
|
||||
_primary_email = 'email_from'
|
||||
|
||||
name = fields.Char()
|
||||
email_from = fields.Char()
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner', 'Customer',
|
||||
compute='_compute_partner_id',
|
||||
store=False)
|
||||
|
||||
@api.depends('email_from')
|
||||
def _compute_partner_id(self):
|
||||
partners = self.env['res.partner'].search(
|
||||
[('email_normalized', 'in', self.filtered('email_from').mapped('email_normalized'))]
|
||||
)
|
||||
self.partner_id = False
|
||||
for record in self.filtered('email_from'):
|
||||
record.partner_id = next(
|
||||
(partner.id for partner in partners
|
||||
if partner.email_normalized == record.email_normalized),
|
||||
False
|
||||
)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class UtmTestSourceMixin(models.Model):
|
||||
""" Test utm.source.mixin """
|
||||
_description = "UTM Source Mixin Test Model"
|
||||
_name = "utm.test.source.mixin"
|
||||
_order = "id DESC"
|
||||
_rec_name = "title"
|
||||
_inherit = [
|
||||
"utm.source.mixin",
|
||||
]
|
||||
|
||||
name = fields.Char(inherited=True)
|
||||
title = fields.Char()
|
||||
|
||||
|
||||
class UtmTestSourceMixinOther(models.Model):
|
||||
""" Test utm.source.mixin, similar to the other one, allowing also to test
|
||||
cross model uniqueness check """
|
||||
_description = "UTM Source Mixin Test Model (another)"
|
||||
_name = "utm.test.source.mixin.other"
|
||||
_order = "id DESC"
|
||||
_rec_name = "title"
|
||||
_inherit = [
|
||||
"mail.thread",
|
||||
"utm.source.mixin",
|
||||
]
|
||||
|
||||
name = fields.Char(inherited=True)
|
||||
title = fields.Char()
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mailing_test_customer_all,access.mailing.test.customer.all,model_mailing_test_customer,,0,0,0,0
|
||||
access_mailing_test_customer_user,access.mailing.test.customer.user,model_mailing_test_customer,base.group_user,1,1,1,1
|
||||
access_mailing_test_simple_all,access.mailing.test.simple.all,model_mailing_test_simple,,0,0,0,0
|
||||
access_mailing_test_simple_user,access.mailing.test.simple.user,model_mailing_test_simple,base.group_user,1,1,1,1
|
||||
access_mailing_test_blacklist_all,access.mailing.test.blacklist.all,model_mailing_test_blacklist,,0,0,0,0
|
||||
access_mailing_test_blacklist_user,access.mailing.test.blacklist.user,model_mailing_test_blacklist,base.group_user,1,1,1,1
|
||||
access_mailing_test_optout_all,access.mailing.test.optout.all,model_mailing_test_optout,,0,0,0,0
|
||||
access_mailing_test_optout_user,access.mailing.test.optout.user,model_mailing_test_optout,base.group_user,1,1,1,1
|
||||
access_mailing_performance_all,access.mailing.performance.all,model_mailing_performance,,0,0,0,0
|
||||
access_mailing_performance_user,access.mailing.performance.user,model_mailing_performance,base.group_user,1,1,1,1
|
||||
access_mailing_performance_blacklist_all,access.mailing.performance.blacklist.all,model_mailing_performance_blacklist,,0,0,0,0
|
||||
access_mailing_performance_blacklist_user,access.mailing.performance.blacklist.user,model_mailing_performance_blacklist,base.group_user,1,1,1,1
|
||||
access_mailing_test_partner_all,access.mailing.test.partner.all,model_mailing_test_partner,,0,0,0,0
|
||||
access_mailing_test_partner_user,access.mailing.test.partner.user,model_mailing_test_partner,base.group_user,1,1,1,1
|
||||
access_mailing_test_partner_unstored_all,access.mailing.test.partner.unstored.all,model_mailing_test_partner_unstored,,0,0,0,0
|
||||
access_mailing_test_partner_unstored_user,access.mailing.test.partner.unstored.user,model_mailing_test_partner_unstored,base.group_user,1,1,1,1
|
||||
access_mailing_test_utm_all,access.mailing.test.utm.all,model_mailing_test_utm,,0,0,0,0
|
||||
access_mailing_test_utm_user,access.mailing.test.utm.user,model_mailing_test_utm,base.group_user,1,1,1,1
|
||||
access_utm_test_source_mixin_user,access.utm.test.source.mixin.user,model_utm_test_source_mixin,base.group_user,1,1,1,1
|
||||
access_utm_test_source_mixin_other_user,access.utm.test.source.mixin.other.user,model_utm_test_source_mixin_other,base.group_user,1,1,1,1
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_blacklist
|
||||
from . import test_blacklist_behavior
|
||||
from . import test_blacklist_mixin
|
||||
from . import test_link_tracker
|
||||
from . import test_link_tracker_sms
|
||||
from . import test_mailing
|
||||
from . import test_mailing_server
|
||||
from . import test_mailing_sms
|
||||
from . import test_mailing_statistics
|
||||
from . import test_mailing_statistics_sms
|
||||
from . import test_mailing_test
|
||||
from . import test_performance
|
||||
from . import test_utm
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
|
||||
from odoo.addons.mass_mailing_sms.tests.common import MassSMSCommon
|
||||
from odoo.addons.test_mail_sms.tests.common import TestSMSCommon
|
||||
|
||||
|
||||
class TestMassMailCommon(MassSMSCommon, TestSMSCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailCommon, cls).setUpClass()
|
||||
|
||||
cls.test_alias = cls.env['mail.alias'].create({
|
||||
'alias_name': 'test.alias',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': cls.env['ir.model']._get('mailing.test.simple').id,
|
||||
'alias_contact': 'everyone'
|
||||
})
|
||||
|
||||
# enforce last update by user_marketing to match _process_mass_mailing_queue
|
||||
# taking last writer as user running a batch
|
||||
cls.mailing_bl = cls.env['mailing.mailing'].with_user(cls.user_marketing).create({
|
||||
'name': 'SourceName',
|
||||
'subject': 'MailingSubject',
|
||||
# `+ ""` is for insuring that _prepend_preview rule out that case
|
||||
'preview': 'Hi {{ object.name + "" }} :)',
|
||||
'body_html': """<div><p>Hello <t t-out="object.name"/></p>,
|
||||
<t t-set="url" t-value="'www.odoo.com'"/>
|
||||
<t t-set="httpurl" t-value="'https://www.odoo.eu'"/>f
|
||||
<span>Website0: <a id="url0" t-attf-href="https://www.odoo.tz/my/{{object.name}}">https://www.odoo.tz/my/<t t-out="object.name"/></a></span>
|
||||
<span>Website1: <a id="url1" href="https://www.odoo.be">https://www.odoo.be</a></span>
|
||||
<span>Website2: <a id="url2" t-attf-href="https://{{url}}">https://<t t-out="url"/></a></span>
|
||||
<span>Website3: <a id="url3" t-att-href="httpurl"><t t-out="httpurl"/></a></span>
|
||||
<span>External1: <a id="url4" href="https://www.example.com/foo/bar?baz=qux">Youpie</a></span>
|
||||
<span>Internal1: <a id="url5" href="/event/dummy-event-0">Internal link</a></span>
|
||||
<span>Internal2: <a id="url6" href="/view"/>View link</a></span>
|
||||
<span>Email: <a id="url7" href="mailto:test@odoo.com">test@odoo.com</a></span>
|
||||
<p>Stop spam ? <a id="url8" role="button" href="/unsubscribe_from_list">Ok</a></p>
|
||||
</div>""",
|
||||
'mailing_type': 'mail',
|
||||
'mailing_model_id': cls.env['ir.model']._get('mailing.test.blacklist').id,
|
||||
'reply_to_mode': 'update',
|
||||
})
|
||||
|
||||
cls.mailing_sms = cls.env['mailing.mailing'].with_user(cls.user_marketing).create({
|
||||
'name': 'XMas SMS',
|
||||
'subject': 'Xmas SMS for {object.name}',
|
||||
'mailing_model_id': cls.env['ir.model']._get('mail.test.sms').id,
|
||||
'mailing_type': 'sms',
|
||||
'mailing_domain': '%s' % repr([('name', 'ilike', 'MassSMSTest')]),
|
||||
'body_plaintext': 'Dear {{object.display_name}} this is a mass SMS with two links http://www.odoo.com/smstest and http://www.odoo.com/smstest/{{object.id}}',
|
||||
'sms_force_send': True,
|
||||
'sms_allow_unsubscribe': True,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _create_test_blacklist_records(cls, model='mailing.test.blacklist', count=1):
|
||||
""" Deprecated, remove in 14.4 """
|
||||
return cls.__create_mailing_test_records(model=model, count=count)
|
||||
|
||||
@classmethod
|
||||
def _create_mailing_sms_test_records(cls, model='mail.test.sms', partners=None, count=1):
|
||||
""" Helper to create data. Currently simple, to be improved. """
|
||||
Model = cls.env[model]
|
||||
phone_field = 'phone_nbr' if 'phone_nbr' in Model else 'phone'
|
||||
partner_field = 'customer_id' if 'customer_id' in Model else 'partner_id'
|
||||
|
||||
vals_list = []
|
||||
for idx in range(count):
|
||||
vals = {
|
||||
'name': 'MassSMSTestRecord_%02d' % idx,
|
||||
phone_field: '045600%02d%02d' % (idx, idx)
|
||||
}
|
||||
if partners:
|
||||
vals[partner_field] = partners[idx % len(partners)]
|
||||
|
||||
vals_list.append(vals)
|
||||
|
||||
return cls.env[model].create(vals_list)
|
||||
|
||||
@classmethod
|
||||
def _create_mailing_test_records(cls, model='mailing.test.blacklist', partners=None, count=1):
|
||||
""" Helper to create data. Currently simple, to be improved. """
|
||||
Model = cls.env[model]
|
||||
email_field = 'email' if 'email' in Model else 'email_from'
|
||||
partner_field = 'customer_id' if 'customer_id' in Model else 'partner_id'
|
||||
|
||||
vals_list = []
|
||||
for x in range(0, count):
|
||||
vals = {
|
||||
'name': 'TestRecord_%02d' % x,
|
||||
email_field: '"TestCustomer %02d" <test.record.%02d@test.example.com>' % (x, x),
|
||||
}
|
||||
if partners:
|
||||
vals[partner_field] = partners[x % len(partners)]
|
||||
|
||||
vals_list.append(vals)
|
||||
|
||||
return cls.env[model].create(vals_list)
|
||||
|
||||
|
||||
class TestMassSMSCommon(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassSMSCommon, cls).setUpClass()
|
||||
cls._test_body = 'Mass SMS in your face'
|
||||
|
||||
records = cls.env['mail.test.sms']
|
||||
partners = cls.env['res.partner']
|
||||
country_be_id = cls.env.ref('base.be').id
|
||||
_country_us_id = cls.env.ref('base.us').id
|
||||
|
||||
for x in range(10):
|
||||
partners += cls.env['res.partner'].with_context(**cls._test_context).create({
|
||||
'name': 'Partner_%s' % (x),
|
||||
'email': '_test_partner_%s@example.com' % (x),
|
||||
'country_id': country_be_id,
|
||||
'mobile': '045600%s%s99' % (x, x)
|
||||
})
|
||||
records += cls.env['mail.test.sms'].with_context(**cls._test_context).create({
|
||||
'name': 'MassSMSTest_%s' % (x),
|
||||
'customer_id': partners[x].id,
|
||||
'phone_nbr': '045600%s%s44' % (x, x)
|
||||
})
|
||||
cls.records = cls._reset_mail_context(records)
|
||||
cls.records_numbers = [phone_validation.phone_format(r.phone_nbr, 'BE', '32', force_format='E164') for r in cls.records]
|
||||
cls.partners = partners
|
||||
|
||||
cls.sms_template = cls.env['sms.template'].create({
|
||||
'name': 'Test Template',
|
||||
'model_id': cls.env['ir.model']._get('mail.test.sms').id,
|
||||
'body': 'Dear {{ object.display_name }} this is a mass SMS.',
|
||||
})
|
||||
|
||||
cls.partner_numbers = [
|
||||
phone_validation.phone_format(partner.mobile, partner.country_id.code, partner.country_id.phone_code, force_format='E164')
|
||||
for partner in partners
|
||||
]
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import users
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
class TestBLAccessRights(common.TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestBLAccessRights, cls).setUpClass()
|
||||
cls._create_portal_user()
|
||||
|
||||
cls.bl_rec = cls.env['mail.blacklist'].create([
|
||||
{'email': 'Not A Stark <john.snow@example.com>'},
|
||||
])
|
||||
cls.bl_previous = cls.env['mail.blacklist'].search([])
|
||||
|
||||
@users('employee')
|
||||
def test_bl_crud_employee(self):
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.blacklist'].create([{'email': 'Arya.Stark@example.com'}])
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).read([])
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).write({'email': 'jaimie.lannister@example.com'})
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).unlink()
|
||||
|
||||
@users('portal_test')
|
||||
def test_bl_crud_portal(self):
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.blacklist'].create([{'email': 'Arya.Stark@example.com'}])
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).read([])
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).write({'email': 'jaimie.lannister@example.com'})
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).unlink()
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_crud_marketing(self):
|
||||
self.env['mail.blacklist'].create([{'email': 'Arya.Stark@example.com'}])
|
||||
|
||||
read_res = self.bl_rec.with_user(self.env.user).read([])
|
||||
self.assertEqual(read_res[0]['id'], self.bl_rec.id)
|
||||
|
||||
self.bl_rec.with_user(self.env.user).write({'email': 'jaimie.lannister@example.com'})
|
||||
self.assertEqual(self.bl_rec.email, 'jaimie.lannister@example.com')
|
||||
|
||||
self.bl_rec.with_user(self.env.user).unlink()
|
||||
|
||||
|
||||
class TestBLConsistency(common.TestMassMailCommon):
|
||||
_base_list = ['Arya.Stark@example.com', 'ned.stark@example.com']
|
||||
|
||||
def setUp(self):
|
||||
super(TestBLConsistency, self).setUp()
|
||||
self.bl_rec = self.env['mail.blacklist'].create([
|
||||
{'email': 'Not A Stark <john.snow@example.com>'},
|
||||
])
|
||||
|
||||
self.bl_previous = self.env['mail.blacklist'].search([])
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_check_case_add(self):
|
||||
""" Test emails case when adding through _add """
|
||||
bl_sudo = self.env['mail.blacklist'].sudo()
|
||||
existing = bl_sudo.create({
|
||||
'email': 'arya.stark@example.com',
|
||||
'active': False,
|
||||
})
|
||||
|
||||
added = self.env['mail.blacklist']._add('Arya.Stark@EXAMPLE.com')
|
||||
self.assertEqual(existing, added)
|
||||
self.assertTrue(existing.active)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_check_case_remove(self):
|
||||
""" Test emails case when deactivating through _remove """
|
||||
bl_sudo = self.env['mail.blacklist'].sudo()
|
||||
existing = bl_sudo.create({
|
||||
'email': 'arya.stark@example.com',
|
||||
'active': True,
|
||||
})
|
||||
|
||||
added = self.env['mail.blacklist']._remove('Arya.Stark@EXAMPLE.com')
|
||||
self.assertEqual(existing, added)
|
||||
self.assertFalse(existing.active)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_create_duplicate(self):
|
||||
""" Test emails are inserted only once if duplicated """
|
||||
bl_sudo = self.env['mail.blacklist'].sudo()
|
||||
self.env['mail.blacklist'].create([
|
||||
{'email': self._base_list[0]},
|
||||
{'email': self._base_list[1]},
|
||||
{'email': 'Another Ned Stark <%s>' % self._base_list[1]},
|
||||
])
|
||||
|
||||
new_bl = bl_sudo.search([('id', 'not in', self.bl_previous.ids)])
|
||||
|
||||
self.assertEqual(len(new_bl), 2)
|
||||
self.assertEqual(
|
||||
set(v.lower() for v in self._base_list),
|
||||
set(v.lower() for v in new_bl.mapped('email'))
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_create_parsing(self):
|
||||
""" Test email is correctly extracted from given entries """
|
||||
bl_sudo = self.env['mail.blacklist'].sudo()
|
||||
self.env['mail.blacklist'].create([
|
||||
{'email': self._base_list[0]},
|
||||
{'email': self._base_list[1]},
|
||||
{'email': 'Not Ned Stark <jaimie.lannister@example.com>'},
|
||||
])
|
||||
|
||||
new_bl = bl_sudo.search([('id', 'not in', self.bl_previous.ids)])
|
||||
|
||||
self.assertEqual(len(new_bl), 3)
|
||||
self.assertEqual(
|
||||
set(v.lower() for v in self._base_list + ['jaimie.lannister@example.com']),
|
||||
set(v.lower() for v in new_bl.mapped('email'))
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_search_exact(self):
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', 'john.snow@example.com')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_search_parsing(self):
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', 'Not A Stark <john.snow@example.com>')])
|
||||
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', '"John J. Snow" <john.snow@example.com>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', 'Aegon? <john.snow@example.com>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', '"John; \"You know Nothing\" Snow" <john.snow@example.com>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_search_case(self):
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', 'john.SNOW@example.COM>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_search_partial(self):
|
||||
search_res = self.env['mail.blacklist'].search([('email', 'ilike', 'John')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
search_res = self.env['mail.blacklist'].search([('email', 'ilike', 'n.SNOW@example.cO>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.mass_mailing.models.mail_thread import BLACKLIST_MAX_BOUNCED_LIMIT
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.sql_db import Cursor
|
||||
|
||||
|
||||
@tagged('mail_blacklist')
|
||||
class TestAutoBlacklist(common.TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestAutoBlacklist, cls).setUpClass()
|
||||
cls.target_rec = cls._create_mailing_test_records()[0]
|
||||
cls.mailing_bl.write({'mailing_domain': [('id', 'in', cls.target_rec.ids)]})
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_bounce_w_auto_bl(self):
|
||||
self._test_mailing_bounce_w_auto_bl(None)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_bounce_w_auto_bl_partner(self):
|
||||
bounced_partner = self.env['res.partner'].sudo().create({
|
||||
'name': 'Bounced Partner',
|
||||
'email': self.target_rec.email_from,
|
||||
'message_bounce': BLACKLIST_MAX_BOUNCED_LIMIT,
|
||||
})
|
||||
self._test_mailing_bounce_w_auto_bl({'bounced_partner': bounced_partner})
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_bounce_w_auto_bl_partner_duplicates(self):
|
||||
bounced_partners = self.env['res.partner'].sudo().create({
|
||||
'name': 'Bounced Partner1',
|
||||
'email': self.target_rec.email_from,
|
||||
'message_bounce': BLACKLIST_MAX_BOUNCED_LIMIT,
|
||||
}) | self.env['res.partner'].sudo().create({
|
||||
'name': 'Bounced Partner2',
|
||||
'email': self.target_rec.email_from,
|
||||
'message_bounce': BLACKLIST_MAX_BOUNCED_LIMIT,
|
||||
})
|
||||
self._test_mailing_bounce_w_auto_bl({'bounced_partner': bounced_partners})
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def _test_mailing_bounce_w_auto_bl(self, bounce_base_values):
|
||||
mailing = self.mailing_bl.with_env(self.env)
|
||||
target = self.target_rec.with_env(self.env)
|
||||
|
||||
# create bounced history of 4 statistics
|
||||
traces = self.env['mailing.trace']
|
||||
for idx in range(4):
|
||||
new_mailing = mailing.copy()
|
||||
new_dt = datetime.datetime.now() - datetime.timedelta(weeks=idx+2)
|
||||
# Cursor.now() uses transaction's timestamp and not datetime lib -> freeze_time
|
||||
# is not sufficient
|
||||
with freeze_time(new_dt), patch.object(Cursor, 'now', lambda *args, **kwargs: new_dt):
|
||||
traces += self._create_bounce_trace(new_mailing, target, dt=datetime.datetime.now() - datetime.timedelta(weeks=idx+2))
|
||||
self.gateway_mail_bounce(new_mailing, target, bounce_base_values)
|
||||
|
||||
# mass mail record: ok, not blacklisted yet
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com'}],
|
||||
mailing, target,
|
||||
check_mail=True
|
||||
)
|
||||
|
||||
# call bounced
|
||||
self.gateway_mail_bounce(mailing, target, bounce_base_values)
|
||||
|
||||
# check blacklist
|
||||
blacklist_record = self.env['mail.blacklist'].sudo().search([('email', '=', target.email_normalized)])
|
||||
self.assertEqual(len(blacklist_record), 1)
|
||||
self.assertTrue(target.is_blacklisted)
|
||||
|
||||
# mass mail record: ko, blacklisted
|
||||
new_mailing = mailing.copy({'mailing_domain': [('id', 'in', target.ids)]})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
new_mailing.action_send_mail()
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
|
||||
new_mailing, target, check_mail=True
|
||||
)
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.models.mailing_models import MailingBLacklist
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
class TestBLMixin(common.TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestBLMixin, cls).setUpClass()
|
||||
|
||||
cls.env['mail.blacklist'].create([{
|
||||
'email': 'Arya.Stark@example.com',
|
||||
'active': True,
|
||||
}, {
|
||||
'email': 'Sansa.Stark@example.com',
|
||||
'active': False,
|
||||
}])
|
||||
|
||||
@users('employee')
|
||||
def test_bl_mixin_primary_field_consistency(self):
|
||||
MailingBLacklist._primary_email = 'not_a_field'
|
||||
with self.assertRaises(UserError):
|
||||
self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', False)])
|
||||
|
||||
MailingBLacklist._primary_email = ['not_a_str']
|
||||
with self.assertRaises(UserError):
|
||||
self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', False)])
|
||||
|
||||
MailingBLacklist._primary_email = 'email_from'
|
||||
self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', False)])
|
||||
|
||||
@users('employee')
|
||||
def test_bl_mixin_is_blacklisted(self):
|
||||
""" Test is_blacklisted field computation """
|
||||
record = self.env['mailing.test.blacklist'].create({'email_from': 'arya.stark@example.com'})
|
||||
self.assertTrue(record.is_blacklisted)
|
||||
|
||||
record = self.env['mailing.test.blacklist'].create({'email_from': 'not.arya.stark@example.com'})
|
||||
self.assertFalse(record.is_blacklisted)
|
||||
|
||||
@users('employee')
|
||||
def test_bl_mixin_search_blacklisted(self):
|
||||
""" Test is_blacklisted field search implementation """
|
||||
record1 = self.env['mailing.test.blacklist'].create({'email_from': 'arya.stark@example.com'})
|
||||
record2 = self.env['mailing.test.blacklist'].create({'email_from': 'not.arya.stark@example.com'})
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', False)])
|
||||
self.assertEqual(search_res, record2)
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '!=', True)])
|
||||
self.assertEqual(search_res, record2)
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', True)])
|
||||
self.assertEqual(search_res, record1)
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '!=', False)])
|
||||
self.assertEqual(search_res, record1)
|
||||
|
||||
@users('employee')
|
||||
def test_bl_mixin_search_blacklisted_format(self):
|
||||
""" Test is_blacklisted field search using email parsing """
|
||||
record1 = self.env['mailing.test.blacklist'].create({'email_from': 'Arya Stark <arya.stark@example.com>'})
|
||||
self.assertTrue(record1.is_blacklisted)
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', True)])
|
||||
self.assertEqual(search_res, record1)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import users
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
|
||||
|
||||
class TestLinkTracker(common.TestMassMailCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLinkTracker, self).setUp()
|
||||
|
||||
self.link = self.env['link.tracker'].search_or_create({
|
||||
'url': 'https://www.example.com'
|
||||
})
|
||||
|
||||
self.click = self.env['link.tracker.click'].create({
|
||||
'link_id': self.link.id,
|
||||
'ip': '100.00.00.00',
|
||||
'country_id': self.env.ref('base.fr').id,
|
||||
})
|
||||
|
||||
def test_add_link(self):
|
||||
code = self.link.code
|
||||
self.assertEqual(self.link.count, 1)
|
||||
|
||||
# click from a new IP should create a new entry
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='BEL'
|
||||
)
|
||||
self.assertEqual(click.ip, '100.00.00.01')
|
||||
self.assertEqual(click.country_id, self.env.ref('base.be'))
|
||||
self.assertEqual(self.link.count, 2)
|
||||
|
||||
# click from same IP (even another country) does not create a new entry
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='FRA'
|
||||
)
|
||||
self.assertEqual(click, None)
|
||||
self.assertEqual(self.link.count, 2)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_add_link_mail_stat(self):
|
||||
record = self.env['mailing.test.blacklist'].create({})
|
||||
code = self.link.code
|
||||
self.assertEqual(self.link.count, 1)
|
||||
trace = self.env['mailing.trace'].create({
|
||||
'mass_mailing_id': self.mailing_bl.id,
|
||||
'model': record._name,
|
||||
'res_id': record.id,
|
||||
})
|
||||
self.assertEqual(trace.trace_status, 'outgoing')
|
||||
self.assertFalse(trace.links_click_datetime)
|
||||
|
||||
# click from a new IP should create a new entry and update stat when provided
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='BEL',
|
||||
mailing_trace_id=trace.id
|
||||
)
|
||||
self.assertEqual(self.link.count, 2)
|
||||
self.assertEqual(click.mass_mailing_id, self.mailing_bl)
|
||||
self.assertTrue(trace.trace_status, 'open')
|
||||
self.assertTrue(trace.links_click_datetime)
|
||||
self.assertEqual(trace.links_click_ids, click)
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('link_tracker')
|
||||
class TestSMSPost(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSMSPost, cls).setUpClass()
|
||||
cls._test_body = 'VOID CONTENT'
|
||||
|
||||
cls.sms_all = cls.env['sms.sms']
|
||||
for x in range(10):
|
||||
cls.sms_all |= cls.env['sms.sms'].create({
|
||||
'number': '+324560000%s%s' % (x, x),
|
||||
'body': cls._test_body,
|
||||
})
|
||||
|
||||
# tracking info
|
||||
cls.utm_c = cls.env['utm.campaign'].create({
|
||||
'name': 'UTM C',
|
||||
'stage_id': cls.env.ref('utm.default_utm_stage').id,
|
||||
'is_auto_campaign': True,
|
||||
})
|
||||
cls.utm_m = cls.env.ref('mass_mailing_sms.utm_medium_sms')
|
||||
cls.tracker_values = {
|
||||
'campaign_id': cls.utm_c.id,
|
||||
'medium_id': cls.utm_m.id,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestSMSPost, self).setUp()
|
||||
self._web_base_url = 'https://test.odoo.com'
|
||||
self.env['ir.config_parameter'].sudo().set_param('web.base.url', self._web_base_url)
|
||||
|
||||
def test_body_link_shorten(self):
|
||||
link = 'http://www.example.com'
|
||||
self.env['link.tracker'].search([('url', '=', link)]).unlink()
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
|
||||
self.assertNotIn(link, new_body)
|
||||
self.assertLinkShortenedText(new_body, (link, True), {'utm_campaign': self.utm_c.name, 'utm_medium': self.utm_m.name})
|
||||
link = self.env['link.tracker'].search([('url', '=', link)])
|
||||
self.assertIn(link.short_url, new_body)
|
||||
|
||||
link = f'{self._web_base_url}/my/super_page?test[0]=42&toto=áâà#title3'
|
||||
self.env['link.tracker'].search([('url', '=', link)]).unlink()
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
|
||||
self.assertNotIn(link, new_body)
|
||||
self.assertLinkShortenedText(new_body, (link, True), {
|
||||
'utm_campaign': self.utm_c.name,
|
||||
'utm_medium': self.utm_m.name,
|
||||
'test[0]': '42',
|
||||
'toto': 'áâà',
|
||||
})
|
||||
link = self.env['link.tracker'].search([('url', '=', link)])
|
||||
self.assertIn(link.short_url, new_body)
|
||||
# Bugfix: ensure void content convert does not crash
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text(False, self.tracker_values)
|
||||
self.assertFalse(new_body)
|
||||
|
||||
def test_body_link_shorten_wshort(self):
|
||||
link = f'{self._web_base_url}/r/RAOUL'
|
||||
self.env['link.tracker'].search([('url', '=', link)]).unlink()
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
|
||||
self.assertIn(link, new_body)
|
||||
self.assertFalse(self.env['link.tracker'].search([('url', '=', link)]))
|
||||
|
||||
def test_body_link_shorten_wunsubscribe(self):
|
||||
link = f'{self._web_base_url}/sms/3/'
|
||||
self.env['link.tracker'].search([('url', '=', link)]).unlink()
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
|
||||
self.assertIn(link, new_body)
|
||||
self.assertFalse(self.env['link.tracker'].search([('url', '=', link)]))
|
||||
|
||||
def test_sms_body_link_shorten_suffix(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'subject': 'Minimal mailing',
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms').id,
|
||||
'mailing_type': 'sms',
|
||||
})
|
||||
|
||||
sms_0 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}',
|
||||
'number': '10',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
sms_1 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}/r/RAOUL',
|
||||
'number': '11',
|
||||
})
|
||||
sms_2 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}/r/RAOUL',
|
||||
'number': '12',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
sms_3 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}/leodagan/r/RAOUL',
|
||||
'number': '13',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
sms_4 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}/r/RAOUL\nAnd again,\n'
|
||||
f'{self._web_base_url}/r/RAOUL',
|
||||
'number': '14',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
res = (sms_0 | sms_1 | sms_2 | sms_3 | sms_4)._update_body_short_links()
|
||||
self.assertEqual(res[sms_0.id], f'Welcome to {self._web_base_url}')
|
||||
self.assertEqual(res[sms_1.id], f'Welcome to {self._web_base_url}/r/RAOUL')
|
||||
self.assertEqual(res[sms_2.id], f'Welcome to {self._web_base_url}/r/RAOUL/s/%s' % sms_2.id)
|
||||
self.assertEqual(res[sms_3.id], f'Welcome to {self._web_base_url}/leodagan/r/RAOUL')
|
||||
self.assertEqual(
|
||||
res[sms_4.id],
|
||||
f'Welcome to {self._web_base_url}/r/RAOUL/s/{sms_4.id}\nAnd again,\n{self._web_base_url}/r/RAOUL/s/{sms_4.id}')
|
||||
|
|
@ -0,0 +1,590 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger, email_normalize
|
||||
|
||||
|
||||
@tagged('mass_mailing')
|
||||
class TestMassMailing(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailing, cls).setUpClass()
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_gateway_reply(self):
|
||||
customers = self.env['res.partner']
|
||||
for x in range(0, 3):
|
||||
customers |= self.env['res.partner'].create({
|
||||
'name': 'Customer_%02d' % x,
|
||||
'email': '"Customer_%02d" <customer_%02d@test.example.com' % (x, x),
|
||||
})
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestName',
|
||||
'subject': 'TestSubject',
|
||||
'body_html': 'Hello <t t-out="object.name" />',
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
|
||||
'keep_archives': True,
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'mailing_domain': '%s' % [('id', 'in', customers.ids)],
|
||||
})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[1], use_in_reply_to=False)
|
||||
|
||||
# customer2 looses headers
|
||||
mail_mail = self._find_mail_mail_wrecord(customers[2])
|
||||
self.format_and_process(
|
||||
MAIL_TEMPLATE,
|
||||
mail_mail.email_to,
|
||||
mail_mail.reply_to,
|
||||
subject='Re: %s' % mail_mail.subject,
|
||||
extra='',
|
||||
msg_id='<123456.%s.%d@test.example.com>' % (customers[2]._name, customers[2].id),
|
||||
target_model=customers[2]._name, target_field=customers[2]._rec_name,
|
||||
)
|
||||
mailing.flush_recordset()
|
||||
|
||||
# check traces status
|
||||
traces = self.env['mailing.trace'].search([('model', '=', customers._name), ('res_id', 'in', customers.ids)])
|
||||
self.assertEqual(len(traces), 3)
|
||||
customer0_trace = traces.filtered(lambda t: t.res_id == customers[0].id)
|
||||
self.assertEqual(customer0_trace.trace_status, 'reply')
|
||||
customer1_trace = traces.filtered(lambda t: t.res_id == customers[1].id)
|
||||
self.assertEqual(customer1_trace.trace_status, 'reply')
|
||||
customer2_trace = traces.filtered(lambda t: t.res_id == customers[2].id)
|
||||
self.assertEqual(customer2_trace.trace_status, 'sent')
|
||||
|
||||
# check mailing statistics
|
||||
self.assertEqual(mailing.sent, 3)
|
||||
self.assertEqual(mailing.delivered, 3)
|
||||
self.assertEqual(mailing.opened, 2)
|
||||
self.assertEqual(mailing.replied, 2)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_gateway_update(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.optout', count=5)
|
||||
self.assertEqual(len(recipients), 5)
|
||||
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout'),
|
||||
'mailing_domain': [('id', 'in', recipients.ids)]
|
||||
})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': record.email_normalized}
|
||||
for record in recipients],
|
||||
mailing, recipients,
|
||||
mail_links_info=[[
|
||||
('url0', 'https://www.odoo.tz/my/%s' % record.name, True, {}),
|
||||
('url1', 'https://www.odoo.be', True, {}),
|
||||
('url2', 'https://www.odoo.com', True, {}),
|
||||
('url3', 'https://www.odoo.eu', True, {}),
|
||||
('url4', 'https://www.example.com/foo/bar?baz=qux', True, {'baz': 'qux'}),
|
||||
('url5', '%s/event/dummy-event-0' % mailing.get_base_url(), True, {}),
|
||||
# view is not shortened and parsed at sending
|
||||
('url6', '%s/view' % mailing.get_base_url(), False, {}),
|
||||
('url7', 'mailto:test@odoo.com', False, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('url8', '%s/unsubscribe_from_list' % mailing.get_base_url(), False, {}),
|
||||
] for record in recipients],
|
||||
check_mail=True
|
||||
)
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5)
|
||||
|
||||
# simulate a click
|
||||
self.gateway_mail_click(mailing, recipients[0], 'https://www.odoo.be')
|
||||
mailing.invalidate_recordset()
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, clicked=1)
|
||||
|
||||
# simulate a bounce
|
||||
self.assertEqual(recipients[1].message_bounce, 0)
|
||||
self.gateway_mail_bounce(mailing, recipients[1])
|
||||
mailing.invalidate_recordset()
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=4, sent=5, opened=1, clicked=1, bounced=1)
|
||||
self.assertEqual(recipients[1].message_bounce, 1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_recipients(self):
|
||||
""" Test recipient-specific computation, with email, formatting,
|
||||
multi-emails, ... to test corner cases. Blacklist mixin impact is
|
||||
tested. """
|
||||
(customer_mult, customer_fmt, customer_unic,
|
||||
customer_case, customer_weird, customer_weird_2
|
||||
) = self.env['res.partner'].create([
|
||||
{
|
||||
'email': 'customer.multi.1@example.com, "Test Multi 2" <customer.multi.2@example.com>',
|
||||
'name': 'MultiEMail',
|
||||
}, {
|
||||
'email': '"Formatted Customer" <test.customer.format@example.com>',
|
||||
'name': 'FormattedEmail',
|
||||
}, {
|
||||
'email': '"Unicode Customer" <test.customer.😊@example.com>',
|
||||
'name': 'UnicodeEmail',
|
||||
}, {
|
||||
'email': 'TEST.CUSTOMER.CASE@EXAMPLE.COM',
|
||||
'name': 'CaseEmail',
|
||||
}, {
|
||||
'email': 'test.customer.weird@example.com Weird Format',
|
||||
'name': 'WeirdFormatEmail',
|
||||
}, {
|
||||
'email': 'Weird Format2 test.customer.weird.2@example.com',
|
||||
'name': 'WeirdFormatEmail2',
|
||||
}
|
||||
])
|
||||
|
||||
# check difference of email management between a classic model and a model
|
||||
# with an 'email_normalized' field (blacklist mixin)
|
||||
for dst_model in ['mailing.test.customer', 'mailing.test.blacklist']:
|
||||
with self.subTest(dst_model=dst_model):
|
||||
(record_p_mult, record_p_fmt, record_p_unic,
|
||||
record_p_case, record_p_weird, record_p_weird_2,
|
||||
record_mult, record_fmt, record_unic,
|
||||
record_case, recod_weird, record_weird_2
|
||||
) = self.env[dst_model].create([
|
||||
{
|
||||
'customer_id': customer_mult.id,
|
||||
}, {
|
||||
'customer_id': customer_fmt.id,
|
||||
}, {
|
||||
'customer_id': customer_unic.id,
|
||||
}, {
|
||||
'customer_id': customer_case.id,
|
||||
}, {
|
||||
'customer_id': customer_weird.id,
|
||||
}, {
|
||||
'customer_id': customer_weird_2.id,
|
||||
}, {
|
||||
'email_from': 'record.multi.1@example.com, "Record Multi 2" <record.multi.2@example.com>',
|
||||
}, {
|
||||
'email_from': '"Formatted Record" <record.format@example.com>',
|
||||
}, {
|
||||
'email_from': '"Unicode Record" <record.😊@example.com>',
|
||||
}, {
|
||||
'email_from': 'TEST.RECORD.CASE@EXAMPLE.COM',
|
||||
}, {
|
||||
'email_from': 'test.record.weird@example.com Weird Format',
|
||||
}, {
|
||||
'email_from': 'Weird Format2 test.record.weird.2@example.com',
|
||||
}
|
||||
])
|
||||
test_records = (
|
||||
record_p_mult + record_p_fmt + record_p_unic +
|
||||
record_p_case + record_p_weird + record_p_weird_2 +
|
||||
record_mult + record_fmt + record_unic +
|
||||
record_case + recod_weird + record_weird_2
|
||||
)
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'body_html': """<div><p>Hello ${object.name}</p>""",
|
||||
'mailing_domain': [('id', 'in', test_records.ids)],
|
||||
'mailing_model_id': self.env['ir.model']._get_id(dst_model),
|
||||
'mailing_type': 'mail',
|
||||
'name': 'SourceName',
|
||||
'preview': 'Hi ${object.name} :)',
|
||||
'reply_to_mode': 'update',
|
||||
'subject': 'MailingSubject',
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
# Difference in email, email_to_recipients and email_to_mail
|
||||
# -> email: trace email: normalized, to ease its management, mainly technical
|
||||
# -> email_to_mail: mail.mail email: email_to stored in outgoing mail.mail (can be multi)
|
||||
# -> email_to_recipients: email_to for outgoing emails, list means several recipients
|
||||
self.assertMailTraces(
|
||||
[
|
||||
{'email': 'customer.multi.1@example.com, "Test Multi 2" <customer.multi.2@example.com>',
|
||||
'email_to_recipients': [[f'"{customer_mult.name}" <customer.multi.1@example.com>', f'"{customer_mult.name}" <customer.multi.2@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_mult,
|
||||
'trace_status': 'sent'},
|
||||
{'email': '"Formatted Customer" <test.customer.format@example.com>',
|
||||
# mail to avoids double encapsulation
|
||||
'email_to_recipients': [[f'"{customer_fmt.name}" <test.customer.format@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_fmt,
|
||||
'trace_status': 'sent'},
|
||||
{'email': '"Unicode Customer" <test.customer.😊@example.com>',
|
||||
# mail to avoids double encapsulation
|
||||
'email_to_recipients': [[f'"{customer_unic.name}" <test.customer.😊@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_unic,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'TEST.CUSTOMER.CASE@EXAMPLE.COM',
|
||||
'email_to_recipients': [[f'"{customer_case.name}" <test.customer.case@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_case,
|
||||
'trace_status': 'sent'}, # lower cased
|
||||
{'email': 'test.customer.weird@example.com Weird Format',
|
||||
'email_to_recipients': [[f'"{customer_weird.name}" <test.customer.weird@example.comweirdformat>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_weird,
|
||||
'trace_status': 'sent'}, # concatenates everything after domain
|
||||
{'email': 'Weird Format2 test.customer.weird.2@example.com',
|
||||
'email_to_recipients': [[f'"{customer_weird_2.name}" <test.customer.weird.2@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_weird_2,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'record.multi.1@example.com',
|
||||
'email_to_mail': 'record.multi.1@example.com,record.multi.2@example.com',
|
||||
'email_to_recipients': [['record.multi.1@example.com', 'record.multi.2@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'record.format@example.com',
|
||||
'email_to_mail': 'record.format@example.com',
|
||||
'email_to_recipients': [['record.format@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'record.😊@example.com',
|
||||
'email_to_mail': 'record.😊@example.com',
|
||||
'email_to_recipients': [['record.😊@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'test.record.case@example.com',
|
||||
'email_to_mail': 'test.record.case@example.com',
|
||||
'email_to_recipients': [['test.record.case@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'test.record.weird@example.comweirdformat',
|
||||
'email_to_mail': 'test.record.weird@example.comweirdformat',
|
||||
'email_to_recipients': [['test.record.weird@example.comweirdformat']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'test.record.weird.2@example.com',
|
||||
'email_to_mail': 'test.record.weird.2@example.com',
|
||||
'email_to_recipients': [['test.record.weird.2@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
],
|
||||
mailing,
|
||||
test_records,
|
||||
check_mail=True,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_reply_to_mode_new(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.blacklist', count=5)
|
||||
self.assertEqual(len(recipients), 5)
|
||||
initial_messages = recipients.message_ids
|
||||
mailing.write({
|
||||
'mailing_domain': [('id', 'in', recipients.ids)],
|
||||
'keep_archives': False,
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': self.test_alias.display_name,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
answer_rec = self.gateway_mail_reply_wemail(MAIL_TEMPLATE, recipients[0].email_normalized, target_model=self.test_alias.alias_model_id.model)
|
||||
self.assertTrue(bool(answer_rec))
|
||||
self.assertEqual(answer_rec.name, 'Re: %s' % mailing.subject)
|
||||
self.assertEqual(
|
||||
answer_rec.message_ids.subject, 'Re: %s' % mailing.subject,
|
||||
'Answer should be logged')
|
||||
self.assertEqual(recipients.message_ids, initial_messages)
|
||||
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, replied=1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_reply_to_mode_update(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.blacklist', count=5)
|
||||
self.assertEqual(len(recipients), 5)
|
||||
mailing.write({
|
||||
'mailing_domain': [('id', 'in', recipients.ids)],
|
||||
'keep_archives': False,
|
||||
'reply_to_mode': 'update',
|
||||
'reply_to': self.test_alias.display_name,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
answer_rec = self.gateway_mail_reply_wemail(MAIL_TEMPLATE, recipients[0].email_normalized, target_model=self.test_alias.alias_model_id.model)
|
||||
self.assertFalse(bool(answer_rec))
|
||||
self.assertEqual(
|
||||
recipients[0].message_ids[1].subject, mailing.subject,
|
||||
'Should have keep a log (to enable thread-based answer)')
|
||||
self.assertEqual(
|
||||
recipients[0].message_ids[0].subject, 'Re: %s' % mailing.subject,
|
||||
'Answer should be logged')
|
||||
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, replied=1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_trace_utm(self):
|
||||
""" Test mailing UTMs are caught on reply"""
|
||||
self._create_mailing_list()
|
||||
self.test_alias.write({
|
||||
'alias_model_id': self.env['ir.model']._get('mailing.test.utm').id
|
||||
})
|
||||
|
||||
source = self.env['utm.source'].create({'name': 'Source test'})
|
||||
medium = self.env['utm.medium'].create({'name': 'Medium test'})
|
||||
campaign = self.env['utm.campaign'].create({'name': 'Campaign test'})
|
||||
subject = 'MassMailingTestUTM'
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'UTMTest',
|
||||
'subject': subject,
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
|
||||
'keep_archives': True,
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [(4, self.mailing_list_1.id)],
|
||||
'source_id': source.id,
|
||||
'medium_id': medium.id,
|
||||
'campaign_id': campaign.id
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
traces = self.env['mailing.trace'].search([('model', '=', self.mailing_list_1.contact_ids._name), ('res_id', 'in', self.mailing_list_1.contact_ids.ids)])
|
||||
self.assertEqual(len(traces), 3)
|
||||
|
||||
# simulate response to mailing
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[1], use_in_reply_to=False)
|
||||
|
||||
mailing_test_utms = self.env['mailing.test.utm'].search([('name', '=', 'Re: %s' % subject)])
|
||||
self.assertEqual(len(mailing_test_utms), 2)
|
||||
for test_utm in mailing_test_utms:
|
||||
self.assertEqual(test_utm.campaign_id, campaign)
|
||||
self.assertEqual(test_utm.source_id, source)
|
||||
self.assertEqual(test_utm.medium_id, medium)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_blacklist(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(count=5)
|
||||
|
||||
# blacklist records 2, 3, 4
|
||||
self.env['mail.blacklist'].create({'email': recipients[2].email_normalized})
|
||||
self.env['mail.blacklist'].create({'email': recipients[3].email_normalized})
|
||||
self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
|
||||
|
||||
# unblacklist record 2
|
||||
self.env['mail.blacklist'].action_remove_with_reason(
|
||||
recipients[2].email_normalized, "human error"
|
||||
)
|
||||
self.env['mail.blacklist'].flush_model(['active'])
|
||||
|
||||
mailing.write({'mailing_domain': [('id', 'in', recipients.ids)]})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com'},
|
||||
{'email': 'test.record.01@test.example.com'},
|
||||
{'email': 'test.record.02@test.example.com'},
|
||||
{'email': 'test.record.03@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'},
|
||||
{'email': 'test.record.04@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
|
||||
mailing, recipients, check_mail=True
|
||||
)
|
||||
self.assertEqual(mailing.canceled, 2)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_blacklist_nomixin(self):
|
||||
"""Test that blacklist is applied even if the target model doesn't inherit
|
||||
from mail.thread.blacklist."""
|
||||
test_records = self._create_mailing_test_records(model='mailing.test.simple', count=2)
|
||||
self.mailing_bl.write({
|
||||
'mailing_domain': [('id', 'in', test_records.ids)],
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.simple').id,
|
||||
})
|
||||
self.env['mail.blacklist'].create([{
|
||||
'email': test_records[0].email_from,
|
||||
'active': True,
|
||||
}])
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
self.mailing_bl.action_send_mail()
|
||||
self.assertMailTraces([
|
||||
{'email': email_normalize(test_records[0].email_from), 'trace_status': 'cancel', 'failure_type': 'mail_bl'},
|
||||
{'email': email_normalize(test_records[1].email_from), 'trace_status': 'sent'},
|
||||
], self.mailing_bl, test_records, check_mail=False)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_opt_out(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.optout', count=5)
|
||||
|
||||
# optout records 0 and 1
|
||||
(recipients[0] | recipients[1]).write({'opt_out': True})
|
||||
# blacklist records 4
|
||||
self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
|
||||
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout'),
|
||||
'mailing_domain': [('id', 'in', recipients.ids)]
|
||||
})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'},
|
||||
{'email': 'test.record.01@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'},
|
||||
{'email': 'test.record.02@test.example.com'},
|
||||
{'email': 'test.record.03@test.example.com'},
|
||||
{'email': 'test.record.04@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
|
||||
mailing, recipients, check_mail=True
|
||||
)
|
||||
self.assertEqual(mailing.canceled, 3)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_w_seenlist(self):
|
||||
"""
|
||||
Tests whether function `_get_seen_list` is correctly able to identify duplicate emails,
|
||||
even through different batches.
|
||||
Mails use different names to make sure they are recognized as duplicates even without being
|
||||
normalized (e.g.: '"jc" <0@example.com>' and '"vd" <0@example.com>' are duplicates)
|
||||
"""
|
||||
BATCH_SIZE = 5
|
||||
names = ['jc', 'vd']
|
||||
emails = [f'test.{i}@example.com' for i in range(BATCH_SIZE)]
|
||||
records = self.env['mailing.test.partner'].create([{
|
||||
'name': f'test_duplicates {i}', 'email_from': f'"{names[i % 2]}" <{emails[i % BATCH_SIZE]}>'
|
||||
} for i in range(20)])
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'mailing_domain': [('name', 'ilike', 'test_duplicates %')],
|
||||
'mailing_model_id': self.env.ref('test_mass_mailing.model_mailing_test_partner').id,
|
||||
'name': 'test duplicates',
|
||||
'subject': 'test duplicates',
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
for i in range(0, 20, BATCH_SIZE):
|
||||
mailing.action_send_mail(records[i:i + BATCH_SIZE].mapped('id'))
|
||||
self.assertEqual(len(self._mails), BATCH_SIZE)
|
||||
self.assertEqual(mailing.canceled, 15)
|
||||
mails_sent = [email_normalize(mail['email_to'][0]) for mail in self._mails]
|
||||
for email in emails:
|
||||
self.assertEqual(mails_sent.count(email), 1)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_w_seenlist_unstored_partner(self):
|
||||
""" Test seen list when partners are not stored. """
|
||||
test_customers = self.env['res.partner'].sudo().create([
|
||||
{'email': f'"Mailing Partner {idx}" <email.from.{idx}@test.example.com',
|
||||
'name': f'Mailing Partner {idx}',
|
||||
} for idx in range(8)
|
||||
])
|
||||
test_records = self.env['mailing.test.partner.unstored'].create([
|
||||
{'email_from': f'email.from.{idx}@test.example.com',
|
||||
'name': f'Mailing Record {idx}',
|
||||
} for idx in range(10)
|
||||
])
|
||||
self.assertEqual(test_records[:8].partner_id, test_customers)
|
||||
self.assertFalse(test_records[9:].partner_id)
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'body_html': '<p>Marketing stuff for ${object.name}</p>',
|
||||
'mailing_domain': [('id', 'in', test_records.ids)],
|
||||
'mailing_model_id': self.env['ir.model']._get_id('mailing.test.partner.unstored'),
|
||||
'name': 'test',
|
||||
'subject': 'Blacklisted',
|
||||
})
|
||||
|
||||
# create existing traces to check the seen list
|
||||
traces = self._create_sent_traces(
|
||||
mailing,
|
||||
test_records[:3]
|
||||
)
|
||||
traces.flush_model()
|
||||
|
||||
# check remaining recipients effectively check seen list
|
||||
mailing.action_put_in_queue()
|
||||
res_ids = mailing._get_remaining_recipients()
|
||||
self.assertEqual(sorted(res_ids), sorted(test_records[3:].ids))
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
self.assertEqual(len(self._mails), 7, 'Mailing: seen list should contain 3 existing traces')
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_mailing_list_optout(self):
|
||||
""" Test mailing list model specific optout behavior """
|
||||
mailing_contact_1 = self.env['mailing.contact'].create({'name': 'test 1A', 'email': 'test@test.example.com'})
|
||||
mailing_contact_2 = self.env['mailing.contact'].create({'name': 'test 1B', 'email': 'test@test.example.com'})
|
||||
mailing_contact_3 = self.env['mailing.contact'].create({'name': 'test 3', 'email': 'test3@test.example.com'})
|
||||
mailing_contact_4 = self.env['mailing.contact'].create({'name': 'test 4', 'email': 'test4@test.example.com'})
|
||||
mailing_contact_5 = self.env['mailing.contact'].create({'name': 'test 5', 'email': 'test5@test.example.com'})
|
||||
|
||||
# create mailing list record
|
||||
mailing_list_1 = self.env['mailing.list'].create({
|
||||
'name': 'A',
|
||||
'contact_ids': [
|
||||
(4, mailing_contact_1.id),
|
||||
(4, mailing_contact_2.id),
|
||||
(4, mailing_contact_3.id),
|
||||
(4, mailing_contact_5.id),
|
||||
]
|
||||
})
|
||||
mailing_list_2 = self.env['mailing.list'].create({
|
||||
'name': 'B',
|
||||
'contact_ids': [
|
||||
(4, mailing_contact_3.id),
|
||||
(4, mailing_contact_4.id),
|
||||
]
|
||||
})
|
||||
# contact_1 is optout but same email is not optout from the same list
|
||||
# contact 3 is optout in list 1 but not in list 2
|
||||
# contact 5 is optout
|
||||
subs = self.env['mailing.contact.subscription'].search([
|
||||
'|', '|',
|
||||
'&', ('contact_id', '=', mailing_contact_1.id), ('list_id', '=', mailing_list_1.id),
|
||||
'&', ('contact_id', '=', mailing_contact_3.id), ('list_id', '=', mailing_list_1.id),
|
||||
'&', ('contact_id', '=', mailing_contact_5.id), ('list_id', '=', mailing_list_1.id)
|
||||
])
|
||||
subs.write({'opt_out': True})
|
||||
|
||||
# create mass mailing record
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'SourceName',
|
||||
'subject': 'MailingSubject',
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [(4, ml.id) for ml in mailing_list_1 | mailing_list_2],
|
||||
})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test@test.example.com', 'trace_status': 'sent'},
|
||||
{'email': 'test@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_dup'},
|
||||
{'email': 'test3@test.example.com'},
|
||||
{'email': 'test4@test.example.com'},
|
||||
{'email': 'test5@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'}],
|
||||
mailing,
|
||||
mailing_contact_1 + mailing_contact_2 + mailing_contact_3 + mailing_contact_4 + mailing_contact_5,
|
||||
check_mail=True
|
||||
)
|
||||
self.assertEqual(mailing.canceled, 2)
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mass_mailing')
|
||||
class TestMassMailingServer(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailingServer, cls).setUpClass()
|
||||
cls._init_mail_servers()
|
||||
cls.recipients = cls._create_mailing_test_records(model='mailing.test.optout', count=8)
|
||||
|
||||
def test_mass_mailing_server_archived_usage_protection(self):
|
||||
""" Test the protection against using archived server:
|
||||
- servers used cannot be archived
|
||||
- mailing clone of a mailing with an archived server gets the default one instead
|
||||
"""
|
||||
servers = self.env['ir.mail_server'].create([{
|
||||
'name': 'Server 1',
|
||||
'smtp_host': 'archive-test1.smtp.local',
|
||||
}, {
|
||||
'name': 'Server 2',
|
||||
'smtp_host': 'archive-test2.smtp.local',
|
||||
}])
|
||||
self.env['ir.config_parameter'].set_param('mass_mailing.mail_server_id', servers[0].id)
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': 'specific_user@test.com',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
})
|
||||
|
||||
mailing_clone = mailing.copy()
|
||||
self.assertEqual(mailing_clone.mail_server_id.id, servers[0].id,
|
||||
'The clone of a mailing inherits from the server of the copied mailing')
|
||||
with self.assertRaises(UserError, msg='Servers still used as default and for 2 mailings'):
|
||||
servers.action_archive()
|
||||
self.assertTrue(all(server.active for server in servers), 'All servers must be active')
|
||||
self.env['ir.config_parameter'].set_param('mass_mailing.mail_server_id', False)
|
||||
with self.assertRaises(UserError, msg='Servers still used for 2 mailings'):
|
||||
servers.action_archive()
|
||||
self.assertTrue(all(server.active for server in servers), 'All servers must be active')
|
||||
with self.mock_smtplib_connection():
|
||||
mailing.action_send_mail()
|
||||
with self.assertRaises(UserError, msg='Servers still used for 1 mailings'):
|
||||
servers.action_archive()
|
||||
self.assertTrue(all(server.active for server in servers), 'All servers must be active')
|
||||
with self.mock_smtplib_connection():
|
||||
mailing_clone.action_send_mail()
|
||||
servers.action_archive() # Servers no more used -> no error
|
||||
self.assertFalse(servers.filtered('active'), 'All servers must be archived')
|
||||
self.assertFalse(mailing.copy().mail_server_id,
|
||||
'The clone of a mailing with an archived server gets the default one (none here)')
|
||||
servers[1].action_unarchive()
|
||||
self.env['ir.config_parameter'].set_param('mass_mailing.mail_server_id', servers[1].id)
|
||||
mailing_clone = mailing.copy()
|
||||
self.assertEqual(mailing_clone.mail_server_id.id, servers[1].id,
|
||||
'The clone of a mailing with an archived server gets the default one')
|
||||
mailing_clone.action_archive()
|
||||
with self.assertRaises(UserError, msg='Servers still used as default'):
|
||||
servers.action_archive()
|
||||
self.assertTrue(servers[1].active)
|
||||
self.env['ir.config_parameter'].set_param('mass_mailing.mail_server_id', False)
|
||||
servers.action_archive() # Servers no more used -> no error
|
||||
self.assertFalse(servers.filtered('active'), 'All servers must be archived')
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
|
||||
def test_mass_mailing_server_batch(self):
|
||||
"""Test that the right mail server is chosen to send the mailing.
|
||||
|
||||
Test also the envelop and the SMTP headers.
|
||||
"""
|
||||
# Test sending mailing in batch
|
||||
mailings = self.env['mailing.mailing'].create([{
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': 'specific_user@test.com',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
}, {
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': 'unknown_name@test.com',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
}])
|
||||
with self.mock_smtplib_connection():
|
||||
mailings.action_send_mail()
|
||||
self.assertEqual(self.find_mail_server_mocked.call_count, 2, 'Must be called only once per mail from')
|
||||
|
||||
self.assert_email_sent_smtp(
|
||||
smtp_from='specific_user@test.com',
|
||||
message_from='specific_user@test.com',
|
||||
from_filter=self.server_user.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
||||
self.assert_email_sent_smtp(
|
||||
# Must use the bounce address here because the mail server
|
||||
# is configured for the entire domain "test.com"
|
||||
smtp_from=lambda x: 'bounce' in x,
|
||||
message_from='unknown_name@test.com',
|
||||
from_filter=self.server_domain.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
|
||||
def test_mass_mailing_server_default(self):
|
||||
# We do not have a mail server for this address email, so fall back to the
|
||||
# "notifications@domain" email.
|
||||
mailings = self.env['mailing.mailing'].create([{
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': '"Testing" <unknow_email@unknow_domain.com>',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
}])
|
||||
|
||||
with self.mock_smtplib_connection():
|
||||
mailings.action_send_mail()
|
||||
|
||||
self.assertEqual(self.find_mail_server_mocked.call_count, 1)
|
||||
self.assert_email_sent_smtp(
|
||||
smtp_from='notifications@test.com',
|
||||
message_from='"Testing" <notifications@test.com>',
|
||||
from_filter=self.server_notification.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
||||
self.assertEqual(self.find_mail_server_mocked.call_count, 1, 'Must be called only once')
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
|
||||
def test_mass_mailing_server_forced(self):
|
||||
# We force a mail server on one mailing
|
||||
mailings = self.env['mailing.mailing'].create([{
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': self.server_user.from_filter,
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
}, {
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': 'unknow_email@unknow_domain.com',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
'mail_server_id': self.server_notification.id,
|
||||
}])
|
||||
with self.mock_smtplib_connection():
|
||||
mailings.action_send_mail()
|
||||
self.assertEqual(self.find_mail_server_mocked.call_count, 1, 'Must not be called when mail server is forced')
|
||||
|
||||
self.assert_email_sent_smtp(
|
||||
smtp_from='specific_user@test.com',
|
||||
message_from='specific_user@test.com',
|
||||
from_filter=self.server_user.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
||||
self.assert_email_sent_smtp(
|
||||
smtp_from='unknow_email@unknow_domain.com',
|
||||
message_from='unknow_email@unknow_domain.com',
|
||||
from_filter=self.server_notification.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
|
@ -0,0 +1,361 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from ast import literal_eval
|
||||
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassSMSCommon
|
||||
from odoo import exceptions
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mass_mailing', 'mass_mailing_sms')
|
||||
class TestMassSMSInternals(TestMassSMSCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_domain(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Xmas Spam',
|
||||
'subject': 'Xmas Spam',
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms').id,
|
||||
'mailing_type': 'sms',
|
||||
})
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [])
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Xmas Spam',
|
||||
'subject': 'Xmas Spam',
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms.bl').id,
|
||||
'mailing_type': 'sms',
|
||||
})
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [('phone_sanitized_blacklisted', '=', False)])
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_internals(self):
|
||||
with self.with_user('user_marketing'):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Xmas Spam',
|
||||
'subject': 'Xmas Spam',
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms').id,
|
||||
'mailing_type': 'sms',
|
||||
'mailing_domain': '%s' % repr([('name', 'ilike', 'MassSMSTest')]),
|
||||
'sms_template_id': self.sms_template.id,
|
||||
'sms_allow_unsubscribe': False,
|
||||
})
|
||||
|
||||
self.assertEqual(mailing.mailing_model_real, 'mail.test.sms')
|
||||
self.assertEqual(mailing.medium_id, self.env.ref('mass_mailing_sms.utm_medium_sms'))
|
||||
self.assertEqual(mailing.body_plaintext, self.sms_template.body)
|
||||
|
||||
remaining_res_ids = mailing._get_remaining_recipients()
|
||||
self.assertEqual(set(remaining_res_ids), set(self.records.ids))
|
||||
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id,
|
||||
'number': self.records_numbers[i],
|
||||
'content': 'Dear %s this is a mass SMS.' % record.display_name
|
||||
} for i, record in enumerate(self.records)],
|
||||
mailing, self.records,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_internals_errors(self):
|
||||
# same customer, specific different number on record -> should be valid
|
||||
new_record_1 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_nr1',
|
||||
'customer_id': self.partners[0].id,
|
||||
'phone_nbr': '0456999999',
|
||||
})
|
||||
void_record = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_void',
|
||||
'customer_id': False,
|
||||
'phone_nbr': '',
|
||||
})
|
||||
falsy_record_1 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_falsy_1',
|
||||
'customer_id': False,
|
||||
'phone_nbr': 'abcd',
|
||||
})
|
||||
falsy_record_2 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_falsy_2',
|
||||
'customer_id': False,
|
||||
'phone_nbr': '04561122',
|
||||
})
|
||||
bl_record_1 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_bl_1',
|
||||
'customer_id': False,
|
||||
'phone_nbr': '0456110011',
|
||||
})
|
||||
self.env['phone.blacklist'].sudo().create({'number': '0456110011'})
|
||||
# new customer, number already on record -> should be ignored
|
||||
country_be_id = self.env.ref('base.be').id
|
||||
nr2_partner = self.env['res.partner'].create({
|
||||
'name': 'Partner_nr2',
|
||||
'country_id': country_be_id,
|
||||
'mobile': '0456449999',
|
||||
})
|
||||
new_record_2 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_nr2',
|
||||
'customer_id': nr2_partner.id,
|
||||
'phone_nbr': self.records[0].phone_nbr,
|
||||
})
|
||||
records_numbers = self.records_numbers + ['+32456999999']
|
||||
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({'sms_force_send': False}) # force outgoing sms, not sent
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id, 'number': records_numbers[i],
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name}
|
||||
for i, record in enumerate(self.records | new_record_1)],
|
||||
mailing, self.records | new_record_1,
|
||||
)
|
||||
# duplicates
|
||||
self.assertSMSTraces(
|
||||
[{'partner': new_record_2.customer_id, 'number': self.records_numbers[0],
|
||||
'content': 'Dear %s this is a mass SMS' % new_record_2.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_duplicate'}],
|
||||
mailing, new_record_2,
|
||||
)
|
||||
# blacklist
|
||||
self.assertSMSTraces(
|
||||
[{'partner': self.env['res.partner'], 'number': phone_validation.phone_format(bl_record_1.phone_nbr, 'BE', '32', force_format='E164'),
|
||||
'content': 'Dear %s this is a mass SMS' % bl_record_1.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_blacklist'}],
|
||||
mailing, bl_record_1,
|
||||
)
|
||||
# missing number
|
||||
self.assertSMSTraces(
|
||||
[{'partner': self.env['res.partner'], 'number': False,
|
||||
'content': 'Dear %s this is a mass SMS' % void_record.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_number_missing'}],
|
||||
mailing, void_record,
|
||||
)
|
||||
# wrong values
|
||||
self.assertSMSTraces(
|
||||
[{'partner': self.env['res.partner'], 'number': record.phone_nbr,
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_number_format'}
|
||||
for record in falsy_record_1 + falsy_record_2],
|
||||
mailing, falsy_record_1 + falsy_record_2,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_internals_done_ids(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({'sms_force_send': False}) # check with outgoing traces, not already sent
|
||||
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms(res_ids=self.records[:5].ids)
|
||||
|
||||
traces = self.env['mailing.trace'].search([('mass_mailing_id', 'in', mailing.ids)])
|
||||
self.assertEqual(len(traces), 5)
|
||||
# new traces generated
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id, 'number': self.records_numbers[i],
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name}
|
||||
for i, record in enumerate(self.records[:5])],
|
||||
mailing, self.records[:5],
|
||||
)
|
||||
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms(res_ids=self.records.ids)
|
||||
|
||||
# delete old traces (for testing purpose: ease check by deleting old ones)
|
||||
traces.unlink()
|
||||
# new failed traces generated for duplicates
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id, 'number': self.records_numbers[i],
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_duplicate'}
|
||||
for i, record in enumerate(self.records[:5])],
|
||||
mailing, self.records[:5],
|
||||
)
|
||||
# new traces generated
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id, 'number': self.records_numbers[i+5],
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name}
|
||||
for i, record in enumerate(self.records[5:])],
|
||||
mailing, self.records[5:],
|
||||
)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_render_mixin')
|
||||
def test_mass_sms_test_button(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestButton',
|
||||
'subject': 'Subject {{ object.name }}',
|
||||
'preview': 'Preview {{ object.name }}',
|
||||
'state': 'draft',
|
||||
'mailing_type': 'sms',
|
||||
'body_plaintext': 'Hello {{ object.name }}',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
})
|
||||
mailing_test = self.env['mailing.sms.test'].with_user(self.user_marketing).create({
|
||||
'numbers': '+32456001122',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mockSMSGateway():
|
||||
mailing_test.action_send_sms()
|
||||
|
||||
# Test if bad inline_template in the body raises an error
|
||||
mailing.write({
|
||||
'body_plaintext': 'Hello {{ object.name_id.id }}',
|
||||
})
|
||||
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.action_send_sms()
|
||||
|
||||
|
||||
@tagged('mass_mailing', 'mass_mailing_sms')
|
||||
class TestMassSMS(TestMassSMSCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_links(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({
|
||||
'body_plaintext': 'Dear {{ object.display_name }} this is a mass SMS with two links http://www.odoo.com/smstest and http://www.odoo.com/smstest/{{ object.name }}',
|
||||
'sms_template_id': False,
|
||||
'sms_force_send': True,
|
||||
'sms_allow_unsubscribe': True,
|
||||
})
|
||||
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id,
|
||||
'number': self.records_numbers[i],
|
||||
'trace_status': 'sent',
|
||||
'content': 'Dear %s this is a mass SMS with two links' % record.display_name
|
||||
} for i, record in enumerate(self.records)],
|
||||
mailing, self.records,
|
||||
sms_links_info=[[
|
||||
('http://www.odoo.com/smstest', True, {}),
|
||||
('http://www.odoo.com/smstest/%s' % record.name, True, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('unsubscribe', False, {}),
|
||||
] for record in self.records],
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mass_sms_partner_only(self):
|
||||
""" Check sending SMS marketing on models having only a partner_id fields
|
||||
set is working. """
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms.partner').id,
|
||||
})
|
||||
|
||||
records = self.env['mail.test.sms.partner'].create([
|
||||
{'name': 'MassSMSTest on %s' % partner.name,
|
||||
'customer_id': partner.id,
|
||||
} for partner in self.partners
|
||||
])
|
||||
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertEqual(len(mailing.mailing_trace_ids), 10)
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id,
|
||||
'number': record.customer_id.phone_sanitized,
|
||||
'trace_status': 'sent',
|
||||
'content': 'Dear %s this is a mass SMS with two links' % record.display_name
|
||||
} for record in records],
|
||||
mailing, records,
|
||||
sms_links_info=[[
|
||||
('http://www.odoo.com/smstest', True, {}),
|
||||
('http://www.odoo.com/smstest/%s' % record.id, True, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('unsubscribe', False, {}),
|
||||
] for record in records],
|
||||
)
|
||||
|
||||
# add a new record, send -> sent list should not resend traces
|
||||
new_record = self.env['mail.test.sms.partner'].create([
|
||||
{'name': 'Duplicate SMS on %s' % self.partners[0].name,
|
||||
'customer_id': self.partners[0].id,
|
||||
}
|
||||
])
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertEqual(len(mailing.mailing_trace_ids), 11)
|
||||
self.assertSMSTraces(
|
||||
[{'partner': new_record.customer_id,
|
||||
'number': new_record.customer_id.phone_sanitized,
|
||||
'trace_status': 'sent',
|
||||
'content': 'Dear %s this is a mass SMS with two links' % new_record.display_name
|
||||
}],
|
||||
mailing, new_record,
|
||||
sms_links_info=[[
|
||||
('http://www.odoo.com/smstest', True, {}),
|
||||
('http://www.odoo.com/smstest/%s' % new_record.id, True, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('unsubscribe', False, {}),
|
||||
]],
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mass_sms_partner_only_m2m(self):
|
||||
""" Check sending SMS marketing on models having only a m2m to partners
|
||||
is currently not suppored. """
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms.partner.2many').id,
|
||||
})
|
||||
|
||||
self.env['mail.test.sms.partner.2many'].create([
|
||||
{'name': 'MassSMSTest on %s' % partner.name,
|
||||
'customer_ids': [(4, partner.id)],
|
||||
} for partner in self.partners
|
||||
])
|
||||
|
||||
with self.assertRaises(exceptions.UserError), self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mass_sms_w_opt_out(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
recipients = self._create_mailing_sms_test_records(model='mail.test.sms.bl.optout', count=5)
|
||||
|
||||
# optout records 0 and 1
|
||||
(recipients[0] | recipients[1]).write({'opt_out': True})
|
||||
# blacklist records 4
|
||||
# TDE FIXME: sudo should not be necessary
|
||||
self.env['phone.blacklist'].sudo().create({'number': recipients[4].phone_nbr})
|
||||
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms.bl.optout'),
|
||||
'mailing_domain': [('id', 'in', recipients.ids)],
|
||||
})
|
||||
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'number': '+32456000000', 'trace_status': 'cancel', 'failure_type': 'sms_optout'},
|
||||
{'number': '+32456000101', 'trace_status': 'cancel', 'failure_type': 'sms_optout'},
|
||||
{'number': '+32456000202', 'trace_status': 'sent'},
|
||||
{'number': '+32456000303', 'trace_status': 'sent'},
|
||||
{'number': '+32456000404', 'trace_status': 'cancel', 'failure_type': 'sms_blacklist'}],
|
||||
mailing, recipients
|
||||
)
|
||||
self.assertEqual(mailing.canceled, 3)
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import html
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests.common import users
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('digest', 'mass_mailing')
|
||||
class TestMailingStatistics(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailingStatistics, cls).setUpClass()
|
||||
|
||||
cls.user_marketing_2 = mail_new_test_user(
|
||||
cls.env,
|
||||
groups='base.group_user,base.group_partner_manager,mass_mailing.group_mass_mailing_user',
|
||||
login='user_marketing_2',
|
||||
name='Marie Marketing',
|
||||
signature='--\nMarie'
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mass_mailing.models.mailing', 'odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_statistics(self):
|
||||
target_records = self._create_mailing_test_records(model='mailing.test.blacklist', count=11)
|
||||
target_records[10]['email_from'] = False # void email should lead to a 'cancel' trace_status
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
mailing.write({'mailing_domain': [('id', 'in', target_records.ids)], 'user_id': self.user_marketing_2.id})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
# simulate some replies and clicks
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, target_records[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, target_records[1], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, target_records[2], use_in_reply_to=True)
|
||||
self.gateway_mail_click(mailing, target_records[0], 'https://www.odoo.be')
|
||||
self.gateway_mail_click(mailing, target_records[2], 'https://www.odoo.be')
|
||||
self.gateway_mail_click(mailing, target_records[3], 'https://www.odoo.be')
|
||||
|
||||
# check mailing statistics
|
||||
self.assertEqual(mailing.canceled, 1)
|
||||
self.assertEqual(mailing.clicked, 3)
|
||||
self.assertEqual(mailing.clicks_ratio, 30)
|
||||
self.assertEqual(mailing.delivered, 10)
|
||||
self.assertEqual(mailing.opened, 4)
|
||||
self.assertEqual(mailing.opened_ratio, 40)
|
||||
self.assertEqual(mailing.replied, 3)
|
||||
self.assertEqual(mailing.replied_ratio, 30)
|
||||
self.assertEqual(mailing.sent, 10)
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing._action_send_statistics()
|
||||
|
||||
self.assertEqual(len(self._new_mails), 1, "Mailing: a mail should have been created for statistics")
|
||||
mail = self._new_mails[0]
|
||||
# test email values
|
||||
self.assertEqual(mail.author_id, self.user_marketing_2.partner_id)
|
||||
self.assertEqual(mail.email_from, self.user_marketing_2.email_formatted)
|
||||
self.assertEqual(mail.email_to, self.user_marketing_2.email_formatted)
|
||||
self.assertEqual(mail.reply_to, self.company_admin.partner_id.email_formatted)
|
||||
self.assertEqual(mail.state, 'outgoing')
|
||||
# test body content: KPIs
|
||||
body_html = html.fromstring(mail.body_html)
|
||||
kpi_values = body_html.xpath('//div[@data-field="mail"]//*[hasclass("kpi_value")]/text()')
|
||||
self.assertEqual(
|
||||
[t.strip().strip('%') for t in kpi_values],
|
||||
['100', str(mailing.opened_ratio), str(mailing.replied_ratio)]
|
||||
)
|
||||
# test body content: clicks (a bit hackish but hey we are in stable)
|
||||
kpi_click_values = body_html.xpath('//div[hasclass("global_layout")]/table//tr[contains(@style,"color: #888888")]/td[contains(@style,"width: 30%")]/text()')
|
||||
first_link_value = int(kpi_click_values[0].strip().split()[1].strip('()'))
|
||||
self.assertEqual(first_link_value, mailing.clicked)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mass_mailing.models.mailing', 'odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_statistics_wo_user(self):
|
||||
target_records = self._create_mailing_test_records(model='mailing.test.blacklist', count=10)
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
mailing.write({'mailing_domain': [('id', 'in', target_records.ids)], 'user_id': False})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing._action_send_statistics()
|
||||
|
||||
self.assertEqual(len(self._new_mails), 1, "Mailing: a mail should have been created for statistics")
|
||||
mail = self._new_mails[0]
|
||||
# test email values
|
||||
self.assertEqual(mail.author_id, self.user_marketing.partner_id)
|
||||
self.assertEqual(mail.email_from, self.user_marketing.email_formatted)
|
||||
self.assertEqual(mail.email_to, self.user_marketing.email_formatted)
|
||||
self.assertEqual(mail.reply_to, self.company_admin.partner_id.email_formatted)
|
||||
self.assertEqual(mail.state, 'outgoing')
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import html
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassSMSCommon
|
||||
from odoo.tests.common import users
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('digest', 'mass_mailing', 'mass_mailing_sms')
|
||||
class TestMailingStatistics(TestMassSMSCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailingStatistics, cls).setUpClass()
|
||||
|
||||
cls.user_marketing_2 = mail_new_test_user(
|
||||
cls.env,
|
||||
groups='base.group_user,base.group_partner_manager,mass_mailing.group_mass_mailing_user',
|
||||
login='user_marketing_2',
|
||||
name='Marie Marketing',
|
||||
signature='--\nMarie'
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mass_mailing_sms.models.mailing_mailing', 'odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_statistics_sms(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
target_records = self.env['mail.test.sms'].browse(self.records.ids)
|
||||
mailing.write({'mailing_domain': [('id', 'in', target_records.ids)], 'user_id': self.user_marketing_2.id})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
# simulate some replies and clicks
|
||||
self.gateway_sms_click(mailing, target_records[0])
|
||||
self.gateway_sms_click(mailing, target_records[2])
|
||||
self.gateway_sms_click(mailing, target_records[3])
|
||||
|
||||
# check mailing statistics
|
||||
self.assertEqual(mailing.clicked, 3)
|
||||
self.assertEqual(mailing.delivered, 10)
|
||||
self.assertEqual(mailing.opened, 3)
|
||||
self.assertEqual(mailing.opened_ratio, 30)
|
||||
self.assertEqual(mailing.sent, 10)
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing._action_send_statistics()
|
||||
|
||||
self.assertEqual(len(self._new_mails), 1, "Mailing: a mail should have been created for statistics")
|
||||
mail = self._new_mails[0]
|
||||
# test email values
|
||||
self.assertEqual(mail.author_id, self.user_marketing_2.partner_id)
|
||||
self.assertEqual(mail.email_from, self.user_marketing_2.email_formatted)
|
||||
self.assertEqual(mail.email_to, self.user_marketing_2.email_formatted)
|
||||
self.assertEqual(mail.reply_to, self.company_admin.partner_id.email_formatted)
|
||||
self.assertEqual(mail.state, 'outgoing')
|
||||
# test body content: KPIs
|
||||
body_html = html.fromstring(mail.body_html)
|
||||
kpi_values = body_html.xpath('//div[@data-field="sms"]//*[hasclass("kpi_value")]/text()')
|
||||
self.assertEqual(
|
||||
[t.strip().strip('%') for t in kpi_values],
|
||||
['100', str(mailing.clicks_ratio), str(mailing.bounced_ratio)]
|
||||
)
|
||||
# test body content: clicks (a bit hackish but hey we are in stable)
|
||||
kpi_click_values = body_html.xpath('//div[hasclass("global_layout")]/table//tr[contains(@style,"color: #888888")]/td[contains(@style,"width: 30%")]/text()')
|
||||
first_link_value = int(kpi_click_values[0].strip().split()[1].strip('()'))
|
||||
self.assertEqual(first_link_value, mailing.clicked)
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import lxml.html
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.fields import Command
|
||||
from odoo.tests.common import users, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mailing_manage')
|
||||
class TestMailingTest(TestMassMailCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_render_mixin')
|
||||
def test_mailing_test_button(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestButton',
|
||||
'subject': 'Subject {{ object.name }}',
|
||||
'preview': 'Preview {{ object.name }}',
|
||||
'state': 'draft',
|
||||
'mailing_type': 'mail',
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
})
|
||||
mailing_test = self.env['mailing.mailing.test'].create({
|
||||
'email_to': 'test@test.com',
|
||||
'mass_mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# not great but matches send_mail_test, maybe that should be a method
|
||||
# on mailing_test?
|
||||
record = self.env[mailing.mailing_model_real].search([], limit=1)
|
||||
first_child = lxml.html.fromstring(self._mails.pop()['body']).xpath('//body/*[1]')[0]
|
||||
self.assertEqual(first_child.tag, 'div')
|
||||
self.assertIn('display:none', first_child.get('style'),
|
||||
"the preview node should be hidden")
|
||||
self.assertEqual(first_child.text.strip(), "Preview " + record.name,
|
||||
"the preview node should contain the preview text")
|
||||
|
||||
# Test if bad inline_template in the subject raises an error
|
||||
mailing.write({'subject': 'Subject {{ object.name_id.id }}'})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad inline_template in the body raises an error
|
||||
mailing.write({
|
||||
'subject': 'Subject {{ object.name }}',
|
||||
'body_html': '<p>Hello <t t-out="object.name_id.id"/></p>',
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad inline_template in the preview raises an error
|
||||
mailing.write({
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'preview': 'Preview {{ object.name_id.id }}',
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
def test_mailing_test_equals_reality(self):
|
||||
"""
|
||||
Check that both test and real emails will format the qweb and inline placeholders correctly in body and subject.
|
||||
"""
|
||||
self.env['mailing.contact'].search([]).unlink()
|
||||
contact_list = self.env['mailing.list'].create({
|
||||
'name': 'Testers',
|
||||
'contact_ids': [Command.create({
|
||||
'name': 'Mitchell Admin',
|
||||
'email': 'real@real.com',
|
||||
})],
|
||||
})
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestButton',
|
||||
'subject': 'Subject {{ object.name }} <t t-out="object.name"/>',
|
||||
'state': 'draft',
|
||||
'mailing_type': 'mail',
|
||||
'body_html': '<p>Hello {{ object.name }} <t t-out="object.name"/></p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [contact_list.id],
|
||||
})
|
||||
mailing_test = self.env['mailing.mailing.test'].create({
|
||||
'email_to': 'test@test.com',
|
||||
'mass_mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
expected_subject = 'Subject Mitchell Admin <t t-out="object.name"/>'
|
||||
expected_body = 'Hello {{ object.name }} Mitchell Admin'
|
||||
|
||||
self.assertSentEmail(self.env.user.partner_id, ['test@test.com'],
|
||||
subject=expected_subject,
|
||||
body_content=expected_body)
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
# send the mailing
|
||||
mailing.action_launch()
|
||||
self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').method_direct_trigger()
|
||||
|
||||
self.assertSentEmail(self.env.user.partner_id, ['real@real.com'],
|
||||
subject=expected_subject,
|
||||
body_content=expected_body)
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mail.tests.test_performance import BaseMailPerformance
|
||||
from odoo.tests.common import users, warmup
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestMassMailPerformanceBase(BaseMailPerformance):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailPerformanceBase, cls).setUpClass()
|
||||
|
||||
cls.user_marketing = mail_new_test_user(
|
||||
cls.env,
|
||||
groups='base.group_user,mass_mailing.group_mass_mailing_user',
|
||||
login='marketing',
|
||||
name='Martial Marketing',
|
||||
signature='--\nMartial'
|
||||
)
|
||||
|
||||
@tagged('mail_performance', 'post_install', '-at_install')
|
||||
class TestMassMailPerformance(TestMassMailPerformanceBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMassMailPerformance, self).setUp()
|
||||
values = [{
|
||||
'name': 'Recipient %s' % x,
|
||||
'email_from': 'Recipient <rec.%s@example.com>' % x,
|
||||
} for x in range(0, 50)]
|
||||
self.mm_recs = self.env['mailing.performance'].create(values)
|
||||
|
||||
@users('__system__', 'marketing')
|
||||
@warmup
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests')
|
||||
def test_send_mailing(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'body_html': '<p>Hello <a role="button" href="https://www.example.com/foo/bar?baz=qux">quux</a><a role="button" href="/unsubscribe_from_list">Unsubscribe</a></p>',
|
||||
'reply_to_mode': 'new',
|
||||
'mailing_model_id': self.ref('test_mass_mailing.model_mailing_performance'),
|
||||
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
|
||||
})
|
||||
|
||||
# runbot needs +51 compared to local
|
||||
with self.assertQueryCount(__system__=1473, marketing=1474):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(mailing.sent, 50)
|
||||
self.assertEqual(mailing.delivered, 50)
|
||||
|
||||
mails = self.env['mail.mail'].sudo().search([('mailing_id', '=', mailing.id)])
|
||||
self.assertFalse(mails, 'Should have auto-deleted the <mail.mail>')
|
||||
|
||||
|
||||
@tagged('mail_performance', 'post_install', '-at_install')
|
||||
class TestMassMailBlPerformance(TestMassMailPerformanceBase):
|
||||
|
||||
def setUp(self):
|
||||
""" In this setup we prepare 20 blacklist entries. We therefore add
|
||||
20 recipients compared to first test in order to have comparable results. """
|
||||
super(TestMassMailBlPerformance, self).setUp()
|
||||
values = [{
|
||||
'name': 'Recipient %s' % x,
|
||||
'email_from': 'Recipient <rec.%s@example.com>' % x,
|
||||
} for x in range(0, 62)]
|
||||
self.mm_recs = self.env['mailing.performance.blacklist'].create(values)
|
||||
|
||||
for x in range(1, 13):
|
||||
self.env['mail.blacklist'].create({
|
||||
'email': 'rec.%s@example.com' % (x * 5)
|
||||
})
|
||||
self.env.flush_all()
|
||||
|
||||
@users('__system__', 'marketing')
|
||||
@warmup
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests')
|
||||
def test_send_mailing_w_bl(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'body_html': '<p>Hello <a role="button" href="https://www.example.com/foo/bar?baz=qux">quux</a><a role="button" href="/unsubscribe_from_list">Unsubscribe</a></p>',
|
||||
'reply_to_mode': 'new',
|
||||
'mailing_model_id': self.ref('test_mass_mailing.model_mailing_performance_blacklist'),
|
||||
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
|
||||
})
|
||||
|
||||
# runbot needs +51 compared to local
|
||||
with self.assertQueryCount(__system__=1546, marketing=1547):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(mailing.sent, 50)
|
||||
self.assertEqual(mailing.delivered, 50)
|
||||
|
||||
cancelled_mail_count = self.env['mail.mail'].sudo().search([('mailing_id', '=', mailing.id)])
|
||||
self.assertEqual(len(cancelled_mail_count), 12, 'Should not have auto deleted the blacklisted emails')
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
@tagged("utm")
|
||||
class TestUTM(common.TestMassMailCommon):
|
||||
|
||||
@users("employee")
|
||||
def test_utm_source_mixin_name(self):
|
||||
""" Test name management with source mixin, as name should be unique
|
||||
and automatically incremented """
|
||||
sources = self.env["utm.test.source.mixin"].create([
|
||||
{
|
||||
'name': 'Test',
|
||||
'title': 'Test',
|
||||
}
|
||||
for idx in range(5)]
|
||||
)
|
||||
self.assertListEqual(
|
||||
sources.mapped('name'),
|
||||
["Test", "Test [2]", "Test [3]", "Test [4]", "Test [5]"]
|
||||
)
|
||||
|
||||
@users("employee")
|
||||
def test_utm_source_mixin_name_brackets(self):
|
||||
""" Test with brackets """
|
||||
false_dupes = self.env["utm.test.source.mixin"].create([
|
||||
{
|
||||
'name': 'NewTest [2]',
|
||||
'title': 'NewTest',
|
||||
}
|
||||
for idx in range(3)] + [
|
||||
{
|
||||
'name': 'NewTest [3]',
|
||||
'title': 'NewTest',
|
||||
}, {
|
||||
'name': 'NewTest',
|
||||
'title': 'NewTest',
|
||||
}]
|
||||
)
|
||||
self.assertListEqual(
|
||||
false_dupes.mapped('name'),
|
||||
["NewTest [2]", "NewTest", "NewTest [3]", "NewTest [4]", "NewTest [5]"]
|
||||
)
|
||||
|
||||
new_source = self.env["utm.test.source.mixin"].create({
|
||||
"name": "OtherTest [2]",
|
||||
})
|
||||
self.assertEqual(new_source.name, "OtherTest [2]")
|
||||
|
||||
@users("employee")
|
||||
def test_utm_source_mixin_name_cross_model(self):
|
||||
""" Uniqueness of source should be ensured cross models. For this purpose
|
||||
we use two models using the utm.source.mixin, allowing to check conflict
|
||||
management between models. """
|
||||
source_1 = self.env["utm.test.source.mixin"].create({
|
||||
"name": "Test",
|
||||
"title": "Test",
|
||||
})
|
||||
self.assertEqual(source_1.name, "Test")
|
||||
self.assertEqual(source_1.title, "Test")
|
||||
|
||||
source_other_1 = self.env["utm.test.source.mixin.other"].create({
|
||||
"name": "Test",
|
||||
"title": "Test",
|
||||
})
|
||||
self.assertEqual(source_other_1.name, "Test [2]")
|
||||
self.assertEqual(source_other_1.title, "Test")
|
||||
|
||||
source_other_2 = self.env["utm.test.source.mixin.other"].create({
|
||||
"name": "New",
|
||||
"title": "New",
|
||||
})
|
||||
self.assertEqual(source_other_2.name, "New")
|
||||
self.assertEqual(source_other_2.title, "New")
|
||||
|
||||
source_other_2.write({"name": "Test"})
|
||||
self.assertEqual(source_other_2.name, "Test [3]")
|
||||
self.assertEqual(source_other_2.title, "New")
|
||||
|
||||
source_2 = source_1.copy()
|
||||
self.assertEqual(source_2.name, "Test [4]")
|
||||
self.assertEqual(source_2.title, "Test")
|
||||
Loading…
Add table
Add a link
Reference in a new issue