# -*- 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.")