216 lines
13 KiB
Python
216 lines
13 KiB
Python
from odoo.addons.whatsapp.tests.common import MockIncomingWhatsApp, WhatsAppCommon
|
|
from odoo.tests import tagged
|
|
|
|
|
|
@tagged('wa_bsuid')
|
|
class TestWhatsAppBsuid(WhatsAppCommon, MockIncomingWhatsApp):
|
|
|
|
def test_bsuid_is_scoped_per_account(self):
|
|
"""The same partner keeps a distinct BSUID per WhatsApp account (the point of the relation)."""
|
|
sender_phone = '+32468000060'
|
|
bsuid_1 = 'BE.account1uid'
|
|
bsuid_2 = 'BE.account2uid'
|
|
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(self.whatsapp_account, 'Hi', sender_phone, sender_bsuid=bsuid_1)
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(self.whatsapp_account_2, 'Hi', sender_phone, sender_bsuid=bsuid_2)
|
|
|
|
partner = self.env['res.partner'].search([('phone', '=', sender_phone)])
|
|
self.assertEqual(len(partner), 1, "Both inbounds should resolve to the same partner (matched by phone)")
|
|
# one BSUID stored per account, neither clobbering the other
|
|
self.assertEqual(len(partner.wa_bsuid_ids), 2)
|
|
self.assertEqual(partner.with_context(whatsapp_account_id=self.whatsapp_account).wa_bsuid, bsuid_1)
|
|
self.assertEqual(partner.with_context(whatsapp_account_id=self.whatsapp_account_2).wa_bsuid, bsuid_2)
|
|
|
|
def test_channel_found_by_wa_id_when_number_format_mismatches(self):
|
|
"""Make sure we fall back on wa_id if the phone format is somehow different."""
|
|
wa_id = '32468000040'
|
|
partner = self.env['res.partner'].create({'name': 'Known WaId User', 'wa_id': wa_id})
|
|
existing_channel = self.env['discuss.channel'].sudo()._get_whatsapp_channel_from_identifiers(
|
|
self.whatsapp_account, {'number': '+32468000040', 'wa_id': wa_id},
|
|
sender_name='wa_id contact', create_if_not_found=True, related_message=False
|
|
)
|
|
self.assertEqual(existing_channel.whatsapp_partner_id, partner)
|
|
self.assertEqual(existing_channel.whatsapp_partner_id.phone, '+32468000040')
|
|
self.assertFalse(existing_channel.whatsapp_partner_id.wa_bsuid_ids)
|
|
self.assertEqual(existing_channel.whatsapp_partner_id.wa_id, wa_id)
|
|
self.assertEqual(existing_channel.whatsapp_number, '32468000040')
|
|
|
|
found_channel = self.env['discuss.channel'].sudo()._get_whatsapp_channel_from_identifiers(
|
|
self.whatsapp_account,
|
|
{'number': '+321468000040', 'wa_id': wa_id}, # new phone digit is added for all numbers of this country
|
|
sender_name='Known WaId User',
|
|
create_if_not_found=True,
|
|
)
|
|
self.assertEqual(found_channel, existing_channel)
|
|
self.assertEqual(found_channel.whatsapp_number, '32468000040')
|
|
self.assertEqual(partner.phone, '+32468000040') # not updated because we don't know if user updated it on purpose
|
|
|
|
def test_receive_creates_partner_and_channel(self):
|
|
"""An inbound message resolves to a partner+channel for each identifier combination."""
|
|
belgium = self.env.ref('base.be')
|
|
cases = [
|
|
(
|
|
'bsuid-only, no sender name',
|
|
{'sender_bsuid': 'BE.11111111111111111'},
|
|
{'bsuid': 'BE.11111111111111111', 'phone': False, 'country_id': belgium},
|
|
),
|
|
(
|
|
'bsuid-only, with sender name',
|
|
{'sender_bsuid': 'BE.22222222222222222', 'sender_name': 'Hidden User'},
|
|
{'bsuid': 'BE.22222222222222222', 'phone': False, 'country_id': belgium, 'name': 'Hidden User'},
|
|
),
|
|
(
|
|
'phone + bsuid',
|
|
{'sender_phone_number': '+32468000011', 'sender_bsuid': 'BE.33333333333333333',
|
|
'sender_name': 'Both Fields User'},
|
|
{'bsuid': 'BE.33333333333333333', 'phone': '+32468000011'},
|
|
),
|
|
]
|
|
for description, receive_kwargs, expected in cases:
|
|
with self.subTest(description=description):
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(self.whatsapp_account, 'Hello', **receive_kwargs)
|
|
partner = self.env['res.partner'].search([('wa_bsuid_ids.bsuid', '=', expected['bsuid'])])
|
|
self.assertEqual(len(partner), 1, "Partner should be created from the inbound message")
|
|
self.assertEqual(partner.with_context(whatsapp_account_id=self.whatsapp_account).wa_bsuid, expected.pop('bsuid'))
|
|
for field, value in expected.items():
|
|
self.assertEqual(partner[field], value, f"{field} should be {value!r}")
|
|
channel = self.env['discuss.channel'].search([('whatsapp_partner_id', '=', partner.id)])
|
|
self.assertEqual(len(channel), 1)
|
|
self.assertTrue(channel.name, "Channel must have a non-empty name")
|
|
|
|
def test_receive_reuses_existing_channel(self):
|
|
"""A second message from the same sender reuses the existing channel."""
|
|
cases = [
|
|
('phone-only', {'sender_phone_number': '+32468000030'}, [('phone', '=', '+32468000030')]),
|
|
('bsuid-only', {'sender_bsuid': 'BE.77777777777777777'}, [('wa_bsuid_ids.bsuid', '=', 'BE.77777777777777777')]),
|
|
]
|
|
for description, receive_kwargs, partner_domain in cases:
|
|
with self.subTest(description=description):
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(self.whatsapp_account, 'First', **receive_kwargs)
|
|
self._receive_whatsapp_message(self.whatsapp_account, 'Second', **receive_kwargs)
|
|
partner = self.env['res.partner'].search(partner_domain)
|
|
channels = self.env['discuss.channel'].search([('whatsapp_partner_id', '=', partner.id)])
|
|
self.assertEqual(len(channels), 1, "Two messages from the same sender should reuse one channel")
|
|
|
|
def test_receive_updates_partner_bsuid(self):
|
|
"""A bsuid arriving alongside a known phone is written onto the existing partner."""
|
|
belgium_id = self.env.ref('base.be').id
|
|
|
|
# Partner pre-existing in DB (e.g. created from the partner form).
|
|
pre_partner = self.env['res.partner'].create({
|
|
'name': 'Known Partner', 'phone': '+32468000020', 'country_id': belgium_id,
|
|
})
|
|
# Partner created by an earlier phone-only inbound.
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(self.whatsapp_account, 'Hello', '+32468000005')
|
|
post_partner = self.env['res.partner'].search([('phone', '=', '+32468000005')])
|
|
self.assertEqual(len(post_partner), 1, "Phone-only inbound should have created a partner")
|
|
|
|
cases = [
|
|
('partner pre-existing with phone', pre_partner, '+32468000020', 'BE.55555555555555555555'),
|
|
('partner created by an earlier phone-only message',
|
|
post_partner, '+32468000005', 'BE.88888888888888888'),
|
|
]
|
|
for description, partner, sender_phone, bsuid in cases:
|
|
with self.subTest(description=description):
|
|
self.assertFalse(partner.with_context(whatsapp_account_id=self.whatsapp_account).wa_bsuid, "No bsuid before the bsuid-bearing message")
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(
|
|
self.whatsapp_account, 'Hi', sender_phone, sender_bsuid=bsuid,
|
|
)
|
|
self.assertEqual(partner.with_context(whatsapp_account_id=self.whatsapp_account).wa_bsuid, bsuid)
|
|
|
|
def test_reply_uses_correct_recipient(self):
|
|
"""A reply via channel.message_post routes through the right identifier."""
|
|
cases = [
|
|
(
|
|
'bsuid-only sender -> reply via bsuid',
|
|
{'sender_bsuid': 'BE.88888888888888888'},
|
|
{'kind': 'bsuid', 'bsuid': 'BE.88888888888888888', 'mobile_number': False},
|
|
),
|
|
(
|
|
'phone + bsuid sender -> reply via phone (number takes priority)',
|
|
{'sender_phone_number': '+32468000040', 'sender_bsuid': 'BE.99999999999999999'},
|
|
{'kind': 'number', 'bsuid': 'BE.99999999999999999', 'mobile_number': '+32468000040'},
|
|
),
|
|
]
|
|
for description, receive_kwargs, expected in cases:
|
|
with self.subTest(description=description):
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(self.whatsapp_account, 'Hi', **receive_kwargs)
|
|
partner = self.env['res.partner'].search([('wa_bsuid_ids.bsuid', '=', expected['bsuid'])])
|
|
channel = self.env['discuss.channel'].search([('whatsapp_partner_id', '=', partner.id)])
|
|
self.assertEqual(len(channel), 1)
|
|
|
|
with self.mockWhatsappGateway():
|
|
wa_msg = channel.message_post(body='Reply', message_type='whatsapp_message').wa_message_ids
|
|
sent_recipients = self._wa_msg_sent_recipients
|
|
|
|
self.assertEqual(len(wa_msg), 1)
|
|
self.assertEqual(wa_msg.state, 'sent')
|
|
# the message was actually sent through the expected identifier
|
|
self.assertEqual(len(sent_recipients), 1)
|
|
if expected['kind'] == 'number':
|
|
self.assertEqual(sent_recipients[0]['number'], wa_msg.mobile_number_formatted)
|
|
self.assertFalse(sent_recipients[0]['bsuid'])
|
|
else:
|
|
self.assertEqual(sent_recipients[0]['bsuid'], wa_msg.recipient_wa_bsuid)
|
|
self.assertFalse(sent_recipients[0]['number'])
|
|
self.assertEqual(wa_msg.recipient_wa_bsuid, expected['bsuid'])
|
|
self.assertEqual(wa_msg.mobile_number, expected['mobile_number'])
|
|
|
|
def test_same_bsuid_on_two_partners_does_not_crash(self):
|
|
"""Updating a partner with a bsuid used by another partner is fine."""
|
|
account = self.whatsapp_account
|
|
shared_bsuid = 'BE.111111111'
|
|
sender_number = '32468000000'
|
|
ResPartner = self.env['res.partner'].sudo().with_context(whatsapp_account_id=account)
|
|
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(account, 'Hi, I just saw your ad', sender_bsuid=shared_bsuid)
|
|
incoming_partner = ResPartner.search([('wa_bsuid_ids', 'any', [('bsuid', '=', shared_bsuid)])])
|
|
new_channel = self.env['discuss.channel'].search([('whatsapp_partner_id', '=', incoming_partner.id)])
|
|
|
|
user_created_partner = ResPartner.create({'name': 'Known Number', 'phone': f'+{sender_number}'})
|
|
# Send the template as a user not already part of the original channel so that replies are routed
|
|
# to a new channel, matching the phone partner in priority
|
|
self.assertNotIn(self.user_admin.partner_id, new_channel.channel_partner_ids)
|
|
composer = self._instanciate_wa_composer_from_records(
|
|
self.simple_whatsapp_template, user_created_partner, with_user=self.user_admin,
|
|
)
|
|
with self.mockWhatsappGateway():
|
|
composer.action_send_whatsapp_template()
|
|
|
|
# When sending a message directly to a number, whatsapp will include the number in replies
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(account, "Yes I'm interested", sender_phone_number=sender_number, sender_bsuid=shared_bsuid)
|
|
|
|
self.assertEqual(incoming_partner.wa_bsuid, shared_bsuid, "Partner created from an incoming message should have a bsuid.")
|
|
self.assertEqual(user_created_partner.wa_bsuid, shared_bsuid, "Receiving a reply should update the bsuid of the matching partner")
|
|
|
|
def test_status_associates_bsuid_with_outbound(self):
|
|
"""A status webhook carrying user_id writes recipient_wa_bsuid onto the matching outbound."""
|
|
sender_phone = '+32468000050'
|
|
bsuid = 'BE.statusuid_01'
|
|
|
|
with self.mockWhatsappGateway():
|
|
self._receive_whatsapp_message(self.whatsapp_account, 'Hi', sender_phone)
|
|
channel = self.env['discuss.channel'].search([('whatsapp_number', '=', sender_phone.lstrip('+'))])
|
|
with self.mockWhatsappGateway():
|
|
wa_msg = channel.message_post(body='Reply', message_type='whatsapp_message').wa_message_ids
|
|
self.assertFalse(wa_msg.recipient_wa_bsuid, "No bsuid known yet at send time")
|
|
|
|
self._receive_message_update(
|
|
self.whatsapp_account,
|
|
'12345678912', # match the arbitrary value of receive_whatsapp_message
|
|
extra_value={
|
|
'statuses': [{'id': wa_msg.msg_uid, 'status': 'sent'}],
|
|
'contacts': [{'wa_id': '32468000050', 'user_id': bsuid}],
|
|
},
|
|
)
|
|
self.assertEqual(wa_msg.recipient_wa_bsuid, bsuid)
|