134 lines
5.6 KiB
Python
134 lines
5.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
import hashlib
|
|
import hmac
|
|
from unittest.mock import patch, MagicMock
|
|
from odoo.tests.common import TransactionCase
|
|
|
|
class TestWhatsAppIntegration(TransactionCase):
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.company = self.env.company
|
|
self.company.write({
|
|
'whatsapp_enabled': True,
|
|
'whatsapp_phone_number_id': '10987654321',
|
|
'whatsapp_access_token': 'test_wa_access_token_abc',
|
|
'whatsapp_verify_token': 'my_custom_verify_token_123',
|
|
'whatsapp_business_phone_number': '15550001111',
|
|
'meta_app_secret': 'super_secret_app_key',
|
|
})
|
|
|
|
self.partner = self.env['res.partner'].create({
|
|
'name': 'Sarah Customer',
|
|
'mobile': '+1 (555) 234-5678',
|
|
'email': 'sarah@example.com',
|
|
})
|
|
|
|
self.product = self.env['product.product'].create({
|
|
'name': 'Pasta Primavera',
|
|
'lst_price': 18.00,
|
|
})
|
|
|
|
# Ensure default template exists
|
|
self.template_confirmed = self.env['social.whatsapp.template'].search([
|
|
('event_type', '=', 'order_confirmed'),
|
|
('company_id', '=', self.company.id)
|
|
], limit=1)
|
|
if not self.template_confirmed:
|
|
self.template_confirmed = self.env['social.whatsapp.template'].create({
|
|
'name': 'Test Order Confirmed',
|
|
'event_type': 'order_confirmed',
|
|
'template_name': 'order_confirmation_v1',
|
|
'language_code': 'en_US',
|
|
'company_id': self.company.id,
|
|
'is_active': True,
|
|
})
|
|
|
|
def test_01_phone_sanitization(self):
|
|
"""Verify phone numbers are correctly sanitized to international digits."""
|
|
wa_api = self.env['social.whatsapp.api']
|
|
self.assertEqual(wa_api.format_phone('+1 (555) 234-5678'), '15552345678')
|
|
self.assertEqual(wa_api.format_phone('0555-123-456'), '0555123456')
|
|
|
|
def test_02_webhook_signature_verification(self):
|
|
"""Verify HMAC-SHA256 signature verification."""
|
|
wa_api = self.env['social.whatsapp.api']
|
|
app_secret = "super_secret_app_key"
|
|
payload = b'{"object": "whatsapp_business_account"}'
|
|
|
|
mac = hmac.new(app_secret.encode('utf-8'), payload, hashlib.sha256)
|
|
valid_header = f"sha256={mac.hexdigest()}"
|
|
|
|
self.assertTrue(wa_api.verify_webhook_signature(payload, valid_header, app_secret))
|
|
self.assertFalse(wa_api.verify_webhook_signature(payload, "sha256=invalid_hash", app_secret))
|
|
|
|
@patch('requests.post')
|
|
def test_03_order_confirmation_notification_dispatch(self, mock_post):
|
|
"""Verify confirming a sale order creates a notification and prevents duplicates."""
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {'messages': [{'id': 'wamid.HBgLMTU1NTIzNDU2NzgVAgASGBQzQTEx'}]}
|
|
mock_post.return_value = mock_response
|
|
|
|
order = self.env['sale.order'].create({
|
|
'partner_id': self.partner.id,
|
|
'order_line': [(0, 0, {
|
|
'product_id': self.product.id,
|
|
'product_uom_qty': 2,
|
|
'price_unit': 18.00,
|
|
})],
|
|
})
|
|
|
|
order.action_confirm()
|
|
|
|
notif = self.env['social.whatsapp.notification'].search([
|
|
('sale_order_id', '=', order.id),
|
|
('event_type', '=', 'order_confirmed')
|
|
], limit=1)
|
|
|
|
self.assertTrue(notif, "A WhatsApp notification record should be created upon order confirmation.")
|
|
self.assertEqual(notif.phone_number, '15552345678')
|
|
self.assertEqual(notif.state, 'sent')
|
|
self.assertEqual(notif.meta_message_id, 'wamid.HBgLMTU1NTIzNDU2NzgVAgASGBQzQTEx')
|
|
|
|
# Test Deduplication: Calling send_order_notification again must return existing record without creating a second one
|
|
duplicate_attempt = self.env['social.whatsapp.notification'].send_order_notification(
|
|
order=order, event_type='order_confirmed'
|
|
)
|
|
self.assertEqual(duplicate_attempt.id, notif.id)
|
|
count = self.env['social.whatsapp.notification'].search_count([
|
|
('sale_order_id', '=', order.id),
|
|
('event_type', '=', 'order_confirmed')
|
|
])
|
|
self.assertEqual(count, 1, "Duplicate notifications must be suppressed.")
|
|
|
|
def test_04_inbound_message_processing_and_order_matching(self):
|
|
"""Verify customer inbound WhatsApp messages are linked to partner and identified order."""
|
|
order = self.env['sale.order'].create({
|
|
'partner_id': self.partner.id,
|
|
'name': 'SO9999',
|
|
'order_line': [(0, 0, {
|
|
'product_id': self.product.id,
|
|
'product_uom_qty': 1,
|
|
'price_unit': 18.00,
|
|
})],
|
|
})
|
|
|
|
msg_payload = {
|
|
'id': 'wamid.HBgLMTU1NTIzNDU2NzgVAgASGBQzQTEx',
|
|
'from': '15552345678',
|
|
'type': 'text',
|
|
'text': {'body': 'Hello, where is my order SO9999?'}
|
|
}
|
|
|
|
msg_model = self.env['social.whatsapp.message']
|
|
msg = msg_model.process_incoming_message(msg_payload, company=self.company)
|
|
|
|
self.assertTrue(msg)
|
|
self.assertEqual(msg.partner_id.id, self.partner.id, "Customer partner should be automatically identified.")
|
|
self.assertEqual(msg.sale_order_id.id, order.id, "Order SO9999 should be automatically matched from message text.")
|
|
|
|
# Test duplicate webhook idempotence
|
|
dup_msg = msg_model.process_incoming_message(msg_payload, company=self.company)
|
|
self.assertEqual(dup_msg.id, msg.id, "Duplicate webhook events with same wamid must be ignored.")
|