418 lines
19 KiB
Python
418 lines
19 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from freezegun import freeze_time
|
|
|
|
from odoo import exceptions
|
|
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
|
|
from odoo.addons.whatsapp.tests.common import WhatsAppCommon
|
|
from odoo.fields import Datetime
|
|
from odoo.tests import tagged, users
|
|
|
|
|
|
class WhatsAppComposerCase(WhatsAppCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
# WRITE access on partner is required to be able to post a message on it
|
|
cls.user_employee.write({'groups_id': [(4, cls.env.ref('base.group_partner_manager').id)]})
|
|
|
|
# test records for sending messages
|
|
cls.customers = cls.env['res.partner'].create([
|
|
{
|
|
'country_id': cls.env.ref('base.in').id,
|
|
'name': 'Customer-IN',
|
|
'mobile': "+91 12345 67891",
|
|
}, {
|
|
'country_id': cls.env.ref('base.be').id,
|
|
'name': 'Customer-BE',
|
|
'mobile': "0456001122",
|
|
}
|
|
])
|
|
|
|
# templates (considered as approved)
|
|
cls.template_basic, cls.template_dynamic_cplx = cls.env['whatsapp.template'].create([
|
|
{
|
|
'body': 'Hello World',
|
|
'name': 'Test-basic',
|
|
'status': 'approved',
|
|
'wa_account_id': cls.whatsapp_account.id,
|
|
}, {
|
|
'body': '''Hello I am {{1}},
|
|
Here my mobile number: {{2}},
|
|
You are coming from {{3}}.
|
|
Welcome to {{4}} office''',
|
|
'name': 'Test-dynamic-complex',
|
|
'status': 'approved',
|
|
'variable_ids': [
|
|
(5, 0, 0),
|
|
(0, 0, {'name': "{{1}}", 'line_type': "body", 'field_type': "user_name", 'demo_value': "Jigar"}),
|
|
(0, 0, {'name': "{{2}}", 'line_type': "body", 'field_type': "user_mobile", 'demo_value': "+91 12345 12345"}),
|
|
(0, 0, {'name': "{{3}}", 'line_type': "body", 'field_type': "field", 'demo_value': "sample country", 'field_name': 'country_id.name'}),
|
|
(0, 0, {'name': "{{4}}", 'line_type': "body", 'field_type': "free_text", 'demo_value': "Odoo In"}),
|
|
],
|
|
'wa_account_id': cls.whatsapp_account.id,
|
|
}
|
|
])
|
|
|
|
|
|
@tagged('wa_composer')
|
|
class WhatsAppComposerInternals(WhatsAppComposerCase, CronMixinCase):
|
|
|
|
def test_assert_initial_data(self):
|
|
""" Ensure base data for tests, to ease understanding them """
|
|
self.assertEqual(self.company_admin.country_id, self.env.ref('base.us'))
|
|
self.assertEqual(self.user_admin.country_id, self.env.ref('base.be'))
|
|
|
|
@users('employee')
|
|
def test_composer_check_user_number(self):
|
|
""" When using 'user_mobile' in template variables, number should be
|
|
set on sender. """
|
|
template = self.template_dynamic_cplx.with_user(self.env.user)
|
|
|
|
for mobile, should_crash in [
|
|
(False, True),
|
|
('', True),
|
|
('zboing', False)
|
|
]:
|
|
with self.subTest(mobile=mobile):
|
|
self.env.user.mobile = mobile
|
|
|
|
composer_form = self._wa_composer_form(template, self.customers[0])
|
|
composer = composer_form.save()
|
|
if should_crash:
|
|
with self.assertRaises(exceptions.ValidationError), self.mockWhatsappGateway():
|
|
composer.action_send_whatsapp_template()
|
|
else:
|
|
with self.mockWhatsappGateway():
|
|
composer.action_send_whatsapp_template()
|
|
|
|
@users('user_wa_admin')
|
|
def test_composer_free_text_on_template_change(self):
|
|
""" Test free_text and button_dynamic_url fields update on template change
|
|
and make sure it is correctly sent even after modifying it. """
|
|
template_1 = self.env['whatsapp.template'].create({
|
|
'body': 'Template 1 Demo Value: {{1}}',
|
|
'name': 'Demo Template 1',
|
|
'status': 'approved',
|
|
'variable_ids': [
|
|
(5, 0, 0),
|
|
(0, 0, {'name': "{{1}}", 'line_type': 'body', 'field_type': "free_text", 'demo_value': "Sample Value 1"}),
|
|
],
|
|
})
|
|
template_2 = self.env['whatsapp.template'].create({
|
|
'body': 'Template 2 Demo Value: {{1}} and {{2}}',
|
|
'name': 'Demo Template 2',
|
|
'status': 'approved',
|
|
'variable_ids': [
|
|
(5, 0, 0),
|
|
(0, 0, {'name': "{{1}}", 'line_type': 'body', 'field_type': "free_text", 'demo_value': "Sample Value 2"}),
|
|
(0, 0, {'name': "{{2}}", 'line_type': 'body', 'field_type': "free_text", 'demo_value': "Sample Value 3"}),
|
|
],
|
|
})
|
|
btn = {'button_type': 'url', 'url_type': 'dynamic'}
|
|
self._add_button_to_template(template_1, 'Odoo EXPO', website_url='https://www.odoo.com/', **btn)
|
|
self._add_button_to_template(template_2, 'Odoo Bash', website_url='https://runbot.odoo.com/', **btn)
|
|
self._add_button_to_template(template_2, 'Odoo Combat', website_url='https://www.odoo.com/', **btn)
|
|
composer_form = self._wa_composer_form(template_1, from_records=self.customers[0])
|
|
self.assertEqual(composer_form.free_text_1, 'Sample Value 1')
|
|
self.assertEqual(composer_form.free_text_2, False)
|
|
self.assertEqual(composer_form.button_dynamic_url_1, 'https://www.odoo.com/???')
|
|
self.assertEqual(composer_form.button_dynamic_url_2, '')
|
|
# Change template to check free_text values are updated
|
|
composer_form.wa_template_id = template_2
|
|
self.assertEqual(composer_form.free_text_1, 'Sample Value 2')
|
|
self.assertEqual(composer_form.free_text_2, 'Sample Value 3')
|
|
self.assertEqual(composer_form.button_dynamic_url_1, 'https://runbot.odoo.com/???')
|
|
self.assertEqual(composer_form.button_dynamic_url_2, 'https://www.odoo.com/???')
|
|
# Edit demo value and check it is updated after sending message
|
|
composer_form.free_text_1 = 'Edited Value'
|
|
composer_form.button_dynamic_url_1 = 'https://runbot.odoo.com/runbot'
|
|
composer_form.button_dynamic_url_2 = 'https://www.odoo.com/combat'
|
|
with self.mockWhatsappGateway():
|
|
composer = composer_form.save()
|
|
composer.action_send_whatsapp_template()
|
|
self.assertWAMessage(
|
|
"sent",
|
|
fields_values={
|
|
"body": "<p>Template 2 Demo Value: Edited Value and Sample Value 3</p>",
|
|
},
|
|
free_text_json_values={
|
|
"button_dynamic_url_1": 'https://runbot.odoo.com/runbot',
|
|
"button_dynamic_url_2": 'https://www.odoo.com/combat'
|
|
})
|
|
|
|
@users('employee')
|
|
def test_composer_number_on_template_change(self):
|
|
""" Test composer behavior when templates changes, also test contextual
|
|
value that forces the value on top of template phone field path. """
|
|
template_1 = self.template_basic
|
|
template_1.write({'phone_field': 'phone'})
|
|
template_2 = self.env['whatsapp.template'].sudo().create({
|
|
'body': 'Hello world',
|
|
'model_id': self.env['ir.model']._get_id('res.partner'),
|
|
'name': 'Template 1',
|
|
'phone_field' : 'mobile',
|
|
'wa_account_id': self.whatsapp_account.id,
|
|
})
|
|
|
|
# verify phone doesn't change with default phone context key in single mode
|
|
composer_1 = self._wa_composer_form(
|
|
template_1, from_records=self.customers[0],
|
|
with_user=self.env.user, add_context={'default_phone': '+32455998877'},
|
|
)
|
|
self.assertEqual(composer_1.phone, '+32455998877', "Default context value should be used")
|
|
composer_1.wa_template_id = template_2
|
|
self.assertEqual(composer_1.phone, '+32455998877', "Default context value should be kept")
|
|
composer_1.wa_template_id = self.env['whatsapp.template']
|
|
self.assertEqual(composer_1.phone, '+32455998877', "Default context value should be kept")
|
|
|
|
# Verify phone change according to template with no default phone
|
|
composer_2 = self._wa_composer_form(
|
|
template_1, from_records=self.customers[0],
|
|
with_user=self.env.user,
|
|
)
|
|
self.assertTrue(not(composer_2.phone) and not(self.customers[0].phone), # '' != False
|
|
"Phone should be taken from record, phone_field of template 1")
|
|
composer_2.wa_template_id = template_2
|
|
self.assertEqual(composer_2.phone, self.customers[0].mobile,
|
|
"Phone should be taken from record, phone_field of template 2")
|
|
composer_2.wa_template_id = self.env['whatsapp.template']
|
|
self.assertEqual(composer_2.phone, self.customers[0].mobile,
|
|
"Phone should not be reset when there is one")
|
|
|
|
@users('employee')
|
|
def test_composer_number_validation(self):
|
|
""" Test number computation and validation in single / batch mode. Also
|
|
test direct send by cron / delegate behavior. """
|
|
template = self.template_basic.with_env(self.env)
|
|
date_reference = Datetime.from_string('2023-11-22 09:00:00')
|
|
invalid_customer = self.env['res.partner'].sudo().create({
|
|
'country_id': self.env.ref('base.in').id,
|
|
'mobile': "12321",
|
|
'name': 'Customer-IN',
|
|
})
|
|
default_phone_number = "+32455112233"
|
|
all_test_records = invalid_customer + self.customers
|
|
|
|
for test_records, use_default, force_cron, exp_phone, exp_invalid_count, exp_crash, exp_batch, exp_cron_trigger in [
|
|
(
|
|
all_test_records[0], False, False,
|
|
'12321', 1, True, False, False, # single record without cron
|
|
), (
|
|
all_test_records[0], False, True,
|
|
'12321', 1, False, False, True, # single record with cron
|
|
), (
|
|
all_test_records[0], True, False,
|
|
'+32455112233', 0, False, False, False, # no need to force cron in single mode / won't crash as default context value
|
|
), (
|
|
all_test_records, False, False,
|
|
'12321, 911234567891, 0456001122', 1, False, True, True, # batch mode always force cron
|
|
), (
|
|
all_test_records, True, False,
|
|
'+32455112233', 1, False, True, True, # batch mode always force cron
|
|
), (
|
|
all_test_records, False, True,
|
|
'12321, 911234567891, 0456001122', 1, False, True, True
|
|
),
|
|
]:
|
|
with self.subTest(test_records=test_records, use_default=use_default, force_cron=force_cron):
|
|
test_records = test_records.with_env(self.env)
|
|
add_context = {'default_phone': default_phone_number} if use_default else {}
|
|
composer_form = self._wa_composer_form(
|
|
template, from_records=test_records,
|
|
add_context=add_context,
|
|
)
|
|
self.assertEqual(composer_form.batch_mode, exp_batch)
|
|
self.assertEqual(composer_form.invalid_phone_number_count, exp_invalid_count)
|
|
self.assertEqual(composer_form.phone, exp_phone)
|
|
composer = composer_form.save()
|
|
self.assertEqual(composer.phone, exp_phone)
|
|
|
|
# Test that the WhatsApp composer fails validation when there is invalid number.
|
|
with freeze_time(date_reference), \
|
|
self.capture_triggers('whatsapp.ir_cron_send_whatsapp_queue') as captured_triggers, \
|
|
self.mockWhatsappGateway():
|
|
if exp_crash:
|
|
with self.assertRaises(exceptions.UserError):
|
|
composer._send_whatsapp_template(force_send_by_cron=force_cron)
|
|
else:
|
|
composer._send_whatsapp_template(force_send_by_cron=force_cron)
|
|
|
|
# in batch mode: three messages ready to be sent if sent with force_cron parameter, else two messages
|
|
if exp_batch:
|
|
self.assertEqual(len(self._new_wa_msg), 3 if force_cron else 2)
|
|
for exp_contacted in self.customers:
|
|
self.assertWAMessageFromRecord(
|
|
exp_contacted,
|
|
status="outgoing",
|
|
)
|
|
if exp_cron_trigger:
|
|
self.assertEqual(len(captured_triggers.records), 1)
|
|
self.assertEqual(
|
|
captured_triggers.records[0].cron_id,
|
|
self.env.ref('whatsapp.ir_cron_send_whatsapp_queue'))
|
|
self.assertEqual(captured_triggers.records[0].call_at, date_reference)
|
|
else:
|
|
self.assertFalse(captured_triggers.records)
|
|
|
|
@users('employee')
|
|
def test_composer_tpl_button(self):
|
|
""" Test adding buttons on templates """
|
|
for button_values in [
|
|
{'button_type': 'quick_reply'},
|
|
{'button_type': 'phone_number', 'call_number': '+91 (835) 902-5723'},
|
|
{'button_type': 'url', 'website_url': 'https://www.odoo.com'},
|
|
]:
|
|
with self.subTest(button_values=button_values):
|
|
self.template_basic.write({'button_ids': [(5, 0)]})
|
|
self._add_button_to_template(self.template_basic, f"Test {button_values['button_type']}", **button_values)
|
|
|
|
template = self.template_basic.with_env(self.env)
|
|
composer = self._instanciate_wa_composer_from_records(template, from_records=self.customers[0])
|
|
with self.mockWhatsappGateway():
|
|
composer.action_send_whatsapp_template()
|
|
|
|
self.assertWAMessage()
|
|
|
|
def test_composer_variable_evaluation(self):
|
|
""" Test various field paths to check corner cases of evaluation """
|
|
variable = self.template_dynamic_cplx.variable_ids.filtered(lambda v: v.field_name == 'country_id.name')
|
|
template = self.env['whatsapp.template'].create({
|
|
'body': "Please evaluate {{1}}.",
|
|
'name': 'Test-various-field-path',
|
|
'status': 'approved',
|
|
'variable_ids': [
|
|
(5, 0, 0),
|
|
(0, 0, {'name': "{{1}}", 'line_type': "body", 'field_type': "field", 'demo_value': "sample country", 'field_name': 'country_id.name'}),
|
|
],
|
|
'wa_account_id': self.whatsapp_account.id,
|
|
})
|
|
|
|
test_tags = self.env['res.partner.category'].create([
|
|
{'color': idx, 'name': f'Tag{idx}'} for idx in range(3)
|
|
])
|
|
test_partner = self.env['res.partner'].create({
|
|
'category_id': test_tags.ids,
|
|
'color': False,
|
|
'country_id': self.env.ref('base.be').id,
|
|
'mobile': '+32455001122',
|
|
'name': 'Test Partner',
|
|
'title': False,
|
|
})
|
|
|
|
for (field_path, expected_value) in zip(
|
|
[
|
|
# many2one with value
|
|
'country_id', 'country_id.name',
|
|
# many2one without value
|
|
'title', 'title.name',
|
|
# many2many
|
|
'category_id', 'category_id.color', 'category_id.partner_ids',
|
|
# integer without value
|
|
'color',
|
|
], [
|
|
'Belgium', 'Belgium',
|
|
'', '',
|
|
'Tag0 Tag1 Tag2', '0 1 2', 'Test Partner',
|
|
'0',
|
|
]
|
|
):
|
|
with self.subTest(field_path=field_path):
|
|
template.variable_ids.write({
|
|
'field_name': field_path
|
|
})
|
|
|
|
composer = self._instanciate_wa_composer_from_records(template, from_records=test_partner, with_user=self.user_employee)
|
|
with self.mockWhatsappGateway():
|
|
composer.action_send_whatsapp_template()
|
|
self.assertWAMessage(
|
|
mail_message_values={
|
|
'body': f'<p>Please evaluate {expected_value}.</p>',
|
|
}
|
|
)
|
|
|
|
# should crash
|
|
for field_path in [
|
|
# does not exist on distant model
|
|
'country_id.wrong',
|
|
# does not exist
|
|
'wrong',
|
|
# void is not supported
|
|
'', False, None,
|
|
]:
|
|
with self.subTest(field_path=field_path):
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
variable.write({
|
|
'field_name': field_path
|
|
})
|
|
|
|
|
|
@tagged('wa_composer')
|
|
class WhatsAppComposerPreview(WhatsAppComposerCase):
|
|
|
|
@users('user_wa_admin')
|
|
def test_composer_preview(self):
|
|
""" Test preview feature from composer """
|
|
body_var = 'Nishant'
|
|
header_var = 'Jigar'
|
|
template = self.env['whatsapp.template'].create({
|
|
'body': 'Feel *free* to *contact* {{1}}; he is ~great~ ~super~ super great !',
|
|
'footer_text': 'Thank *you*',
|
|
'header_text': 'Header ```Code Content``` {{1}}',
|
|
'header_type': 'text',
|
|
'variable_ids': [
|
|
(5, 0, 0),
|
|
(0, 0, {
|
|
'name': "{{1}}",
|
|
'line_type': 'body',
|
|
'field_type': "free_text",
|
|
'demo_value': body_var,
|
|
}),
|
|
(0, 0, {
|
|
'name': "{{1}}",
|
|
'line_type': 'header',
|
|
'field_type': "free_text",
|
|
'demo_value': header_var,
|
|
}),
|
|
],
|
|
'wa_account_id': self.whatsapp_account.id,
|
|
})
|
|
composer = self._instanciate_wa_composer_from_records(template, from_records=self.customers[0])
|
|
|
|
for expected_str in [
|
|
f'Header <code>Code Content</code> {header_var}',
|
|
f'Feel <b>free</b> to <b>contact</b> {body_var}; he is <s>great</s> <s>super</s> super great !',
|
|
'Thank *you*',
|
|
]:
|
|
self.assertIn(expected_str, composer.preview_whatsapp)
|
|
|
|
|
|
@tagged('wa_composer')
|
|
class WhatsAppComposerUsage(WhatsAppComposerCase):
|
|
|
|
def test_composer_template_send_user_access(self):
|
|
""" Test that WA message send through SUDO-ed flow, involving public
|
|
user """
|
|
self._setup_share_users()
|
|
|
|
for test_user in self.test_public_user + self.test_portal_user + self.user_employee:
|
|
with self.subTest(test_user_login=test_user.login):
|
|
composer = self.env['whatsapp.composer'].with_context(
|
|
active_model=self.customers[0]._name, active_ids=self.customers[0].ids,
|
|
).with_user(test_user).sudo().create({
|
|
'wa_template_id': self.template_dynamic_cplx.id,
|
|
})
|
|
|
|
with self.mockWhatsappGateway():
|
|
composer.sudo().action_send_whatsapp_template()
|
|
self.assertWAMessage(
|
|
"sent",
|
|
fields_values={
|
|
"create_uid": test_user,
|
|
"body": f"<p>Hello I am { test_user.name },<br>Here my mobile number: { test_user.mobile },"
|
|
f"<br>You are coming from { self.customers[0].country_id.name }.<br>Welcome to Odoo In office</p>",
|
|
},
|
|
)
|