diff --git a/kairon/chat/handlers/channels/whatsapp.py b/kairon/chat/handlers/channels/whatsapp.py index d4c6b474b..4c72d6726 100644 --- a/kairon/chat/handlers/channels/whatsapp.py +++ b/kairon/chat/handlers/channels/whatsapp.py @@ -76,7 +76,7 @@ async def message( message.update(metadata) await self._handle_user_message(text, message["from"], message, bot) - async def __handle_meta_payload(self, payload: Dict, metadata: Optional[Dict[Text, Any]], bot: str) -> None: + async def handle_meta_payload(self, payload: Dict, metadata: Optional[Dict[Text, Any]], bot: str) -> None: provider = self.config.get("bsp_type", "meta") access_token = self.__get_access_token() for entry in payload["entry"]: @@ -147,7 +147,7 @@ async def handle_payload(self, request, metadata: Optional[Dict[Text, Any]], bot return msg actor = ActorFactory.get_instance(ActorType.callable_runner.value) - actor.execute(self.__handle_meta_payload, payload, metadata, bot) + actor.execute(self.handle_meta_payload, payload, metadata, bot) return msg def get_business_phone_number_id(self) -> Text: diff --git a/kairon/shared/plugins/ipinfo.py b/kairon/shared/plugins/ipinfo.py index bdb39561b..1f4b1b3cf 100644 --- a/kairon/shared/plugins/ipinfo.py +++ b/kairon/shared/plugins/ipinfo.py @@ -20,7 +20,7 @@ def execute(self, ip: Text, **kwargs): headers = {"user-agent": "IPinfoClient/Python3.8/4.2.1", "accept": "application/json"} token = Utility.environment["plugins"]["location"]["token"] url = f"https://ipinfo.io/batch?token={token}" - tracking_info = Utility.execute_http_request("POST", url, headers=headers, data=ip_list) + tracking_info = Utility.execute_http_request("POST", url, headers=headers, request_body=ip_list) return tracking_info except Exception as e: logger.exception(e) diff --git a/tests/integration_test/chat_service_test.py b/tests/integration_test/chat_service_test.py index b7b44f1b7..3f6ef8cfa 100644 --- a/tests/integration_test/chat_service_test.py +++ b/tests/integration_test/chat_service_test.py @@ -2180,32 +2180,14 @@ def test_whatsapp_valid_button_message_request_without_payload_key(mock_validate @responses.activate -def test_whatsapp_valid_attachment_message_request(): - def _mock_validate_hub_signature(*args, **kwargs): - return True - +@patch.object(ActorFactory, "get_instance") +@patch.object(MessengerHandler, "validate_hub_signature") +def test_whatsapp_valid_attachment_message_request(mock_validate, mock_actor): responses.add("POST", "https://graph.facebook.com/v19.0/12345678/messages", json={}) - responses.add( - "GET", - "https://graph.facebook.com/v19.0/sdfghj567", - json={ - "messaging_product": "whatsapp", - "url": "http://kairon-media.url", - "id": "sdfghj567", - }, - ) - - with patch.object( - MessengerHandler, "validate_hub_signature", _mock_validate_hub_signature - ): - with mock.patch( - "kairon.chat.handlers.channels.whatsapp.Whatsapp._handle_user_message", - autospec=True, - ) as whatsapp_msg_handler: - response = client.post( - f"/api/bot/whatsapp/{bot}/{token}", - headers={"hub.verify_token": "valid"}, - json={ + mock_validate.return_value = True + executor = Mock() + mock_actor.return_value = executor + payload = { "object": "whatsapp_business_account", "entry": [ { @@ -2239,55 +2221,31 @@ def _mock_validate_hub_signature(*args, **kwargs): ], } ], - }, - ) + } + response = client.post( + f"/api/bot/whatsapp/{bot}/{token}", + headers={"hub.verify_token": "valid"}, + json=payload + ) actual = response.json() assert actual == "success" - assert len(whatsapp_msg_handler.call_args[0]) == 5 - assert ( - whatsapp_msg_handler.call_args[0][1] - == '/k_multimedia_msg{"document": "sdfghj567"}' - ) - assert whatsapp_msg_handler.call_args[0][2] == "910123456789" - metadata = whatsapp_msg_handler.call_args[0][3] - metadata.pop("timestamp") - assert metadata == { - "from": "910123456789", - "id": "wappmsg.ID", - "document": {"id": "sdfghj567"}, - "type": "document", - "is_integration_user": True, - "bot": bot, - "account": 1, - "channel_type": "whatsapp", - "bsp_type": "meta", - "display_phone_number": "910123456789", - "phone_number_id": "12345678", - "tabname": "default", - } - assert whatsapp_msg_handler.call_args[0][4] == bot + assert not DeepDiff(executor.execute.call_args[0][1], payload, ignore_order=True) + assert not DeepDiff(executor.execute.call_args[0][2], + {'is_integration_user': True, 'bot': bot, 'account': 1, 'channel_type': 'whatsapp', + 'bsp_type': 'meta', 'tabname': 'default'}, ignore_order=True, + ignore_type_in_groups=[(bson.int64.Int64, int)]) + assert executor.execute.call_args[0][3] == bot @responses.activate -def test_whatsapp_payment_message_request(): - from kairon.shared.chat.data_objects import ChannelLogs - - def _mock_validate_hub_signature(*args, **kwargs): - return True - +@patch.object(ActorFactory, "get_instance") +@patch.object(MessengerHandler, "validate_hub_signature") +def test_whatsapp_payment_message_request(mock_validate, mock_actor): responses.add("POST", "https://graph.facebook.com/v19.0/12345678/messages", json={}) - - with patch.object( - MessengerHandler, "validate_hub_signature", _mock_validate_hub_signature - ): - with mock.patch( - "kairon.chat.handlers.channels.whatsapp.Whatsapp._handle_user_message", - autospec=True, - ) as whatsapp_msg_handler: - response = client.post( - f"/api/bot/whatsapp/{bot}/{token}", - headers={"hub.verify_token": "valid"}, - json={ + mock_validate.return_value = True + executor = Mock() + mock_actor.return_value = executor + payload = { "object": "whatsapp_business_account", "entry": [ { @@ -2332,189 +2290,95 @@ def _mock_validate_hub_signature(*args, **kwargs): ], } ], - }, - ) + } + response = client.post( + f"/api/bot/whatsapp/{bot}/{token}", + headers={"hub.verify_token": "valid"}, + json=payload + ) actual = response.json() assert actual == "success" - time.sleep(5) - assert len(whatsapp_msg_handler.call_args[0]) == 5 - assert whatsapp_msg_handler.call_args[0][1] == '/k_payment_msg{"payment": {"reference_id": "BM3-43D-12", "amount": {"value": 100, "offset": 100}, "currency": "INR", "transaction": {"id": "order_Ovpn6PVVFYbmK3", "type": "razorpay", "status": "success", "created_timestamp": 1724764153, "updated_timestamp": 1724764153, "amount": {"value": 100, "offset": 100}, "currency": "INR", "method": {"type": "upi"}}, "receipt": "receipt-value", "notes": {"key1": "value1", "key2": "value2"}}}' - assert whatsapp_msg_handler.call_args[0][2] == "918657459321" - metadata = whatsapp_msg_handler.call_args[0][3] - metadata.pop("timestamp") - assert metadata == { - 'id': 'wamid.HBgMOTE5NTR1OTkxNjg1FQIAEhgKNDBzOTkxOTI5NgA=', - 'status': 'captured', - 'recipient_id': '919515991234', - 'type': 'payment', - 'payment': { - 'reference_id': 'BM3-43D-12', - 'amount': {'value': 100, 'offset': 100}, - 'currency': 'INR', - 'transaction': { - 'id': 'order_Ovpn6PVVFYbmK3', - 'type': 'razorpay', 'status': 'success', - 'created_timestamp': 1724764153, - 'updated_timestamp': 1724764153, - 'amount': {'value': 100, 'offset': 100}, - 'currency': 'INR', - 'method': {'type': 'upi'}}, - 'receipt': 'receipt-value', - 'notes': {'key1': 'value1', 'key2': 'value2'}}, - 'from': '918657459321', - 'is_integration_user': True, - 'bot': bot, - 'account': 1, - 'channel_type': 'whatsapp', - 'bsp_type': 'meta', - 'tabname': 'default', - 'display_phone_number': '918657459321', - 'phone_number_id': '257191390803220' - } - assert whatsapp_msg_handler.call_args[0][4] == bot - - log = ( - ChannelLogs.objects( - bot=bot, - message_id="wamid.HBgMOTE5NTR1OTkxNjg1FQIAEhgKNDBzOTkxOTI5NgA=", - ) - .get() - .to_mongo() - .to_dict() - ) - assert log["data"] == { - 'reference_id': 'BM3-43D-12', - 'amount': {'value': 100, 'offset': 100}, - 'currency': 'INR', - 'transaction': { - 'id': 'order_Ovpn6PVVFYbmK3', - 'type': 'razorpay', - 'status': 'success', - 'created_timestamp': 1724764153, - 'updated_timestamp': 1724764153, - 'amount': {'value': 100, 'offset': 100}, - 'currency': 'INR', - 'method': {'type': 'upi'} - }, - 'receipt': 'receipt-value', - 'notes': { - 'key1': 'value1', - 'key2': 'value2' - } - } - assert log["status"] == "captured" - assert log["recipient"] == "919515991234" + assert not DeepDiff(executor.execute.call_args[0][1], payload, ignore_order=True) + assert not DeepDiff(executor.execute.call_args[0][2], + {'is_integration_user': True, 'bot': bot, 'account': 1, 'channel_type': 'whatsapp', + 'bsp_type': 'meta', 'tabname': 'default'}, ignore_order=True, + ignore_type_in_groups=[(bson.int64.Int64, int)]) + assert executor.execute.call_args[0][3] == bot @responses.activate -def test_whatsapp_valid_order_message_request(): - def _mock_validate_hub_signature(*args, **kwargs): - return True +@patch.object(ActorFactory, "get_instance") +@patch.object(MessengerHandler, "validate_hub_signature") +def test_whatsapp_valid_order_message_request(mock_validate, mock_actor): + mock_validate.return_value = True + executor = Mock() + mock_actor.return_value = executor - with patch.object( - MessengerHandler, "validate_hub_signature", _mock_validate_hub_signature - ): - with mock.patch( - "kairon.chat.handlers.channels.whatsapp.Whatsapp._handle_user_message", - autospec=True, - ) as whatsapp_msg_handler: - response = client.post( - f"/api/bot/whatsapp/{bot}/{token}", - headers={"hub.verify_token": "valid"}, - json={ - "object": "whatsapp_business_account", - "entry": [ + request_json = { + "object": "whatsapp_business_account", + "entry": [ + { + "id": "108103872212677", + "changes": [ { - "id": "108103872212677", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "919876543210", - "phone_number_id": "108578266683441", - }, - "contacts": [ - { - "profile": {"name": "Hitesh"}, - "wa_id": "919876543210", - } - ], - "messages": [ - { - "from": "919876543210", - "id": "wamid.HBgMOTE5NjU3DMU1MDIyQFIAEhggNzg5MEYwNEIyNDA1Q0IxMzU2QkI0NDc3RTVGMzYxNUEA", - "timestamp": "1691598412", - "type": "order", - "order": { - "catalog_id": "538971028364699", - "product_items": [ - { - "product_retailer_id": "akuba13e44", - "quantity": 1, - "item_price": 200, - "currency": "INR", - }, - { - "product_retailer_id": "0z10aj0bmq", - "quantity": 1, - "item_price": 600, - "currency": "INR", - }, - ], + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "919876543210", + "phone_number_id": "108578266683441", + }, + "contacts": [ + { + "profile": {"name": "Hitesh"}, + "wa_id": "919876543210", + } + ], + "messages": [ + { + "from": "919876543210", + "id": "wamid.HBgMOTE5NjU3DMU1MDIyQFIAEhggNzg5MEYwNEIyNDA1Q0IxMzU2QkI0NDc3RTVGMzYxNUEA", + "timestamp": "1691598412", + "type": "order", + "order": { + "catalog_id": "538971028364699", + "product_items": [ + { + "product_retailer_id": "akuba13e44", + "quantity": 1, + "item_price": 200, + "currency": "INR", }, - } - ], - }, - "field": "messages", - } - ], + { + "product_retailer_id": "0z10aj0bmq", + "quantity": 1, + "item_price": 600, + "currency": "INR", + }, + ], + }, + } + ], + }, + "field": "messages", } ], - }, - ) + } + ], + } + + response = client.post( + f"/api/bot/whatsapp/{bot}/{token}", + headers={"hub.verify_token": "valid"}, + json=request_json, + ) actual = response.json() assert actual == "success" - time.sleep(5) - assert len(whatsapp_msg_handler.call_args[0]) == 5 - assert ( - whatsapp_msg_handler.call_args[0][1] - == '/k_order_msg{"order": {"catalog_id": "538971028364699", "product_items": [{"product_retailer_id": "akuba13e44", "quantity": 1, "item_price": 200, "currency": "INR"}, {"product_retailer_id": "0z10aj0bmq", "quantity": 1, "item_price": 600, "currency": "INR"}]}}' - ) - assert whatsapp_msg_handler.call_args[0][2] == "919876543210" - metadata = whatsapp_msg_handler.call_args[0][3] - metadata.pop("timestamp") - assert metadata == { - "from": "919876543210", - "id": "wamid.HBgMOTE5NjU3DMU1MDIyQFIAEhggNzg5MEYwNEIyNDA1Q0IxMzU2QkI0NDc3RTVGMzYxNUEA", - "type": "order", - "order": { - "catalog_id": "538971028364699", - "product_items": [ - { - "product_retailer_id": "akuba13e44", - "quantity": 1, - "item_price": 200, - "currency": "INR", - }, - { - "product_retailer_id": "0z10aj0bmq", - "quantity": 1, - "item_price": 600, - "currency": "INR", - }, - ], - }, - "is_integration_user": True, - "bot": bot, - "account": 1, - "channel_type": "whatsapp", - "bsp_type": "meta", - "tabname": "default", - "display_phone_number": "919876543210", - "phone_number_id": "108578266683441", - } - assert whatsapp_msg_handler.call_args[0][4] == bot + args, kwargs = executor.execute.call_args + assert len(executor.execute.call_args[0]) == 4 + assert args[3] == bot + assert not DeepDiff(args[1], request_json) + assert not DeepDiff(args[2], {'is_integration_user': True, 'bot': bot, 'account': 1, 'channel_type': 'whatsapp', 'bsp_type': 'meta', 'tabname': 'default'}, ignore_order=True, + ignore_type_in_groups=[(int, bson.int64.Int64)]) @responses.activate diff --git a/tests/unit_test/channels/whatsapp_test.py b/tests/unit_test/channels/whatsapp_test.py new file mode 100644 index 000000000..780157ece --- /dev/null +++ b/tests/unit_test/channels/whatsapp_test.py @@ -0,0 +1,270 @@ +from mock import patch +import pytest +import os + +from kairon.shared.utils import Utility +from mongoengine import connect + +class TestWhatsappHandler: + + @pytest.fixture(autouse=True, scope='class') + def setup(self): + os.environ["system_file"] = "./tests/testing_data/system.yaml" + Utility.load_environment() + Utility.load_system_metadata() + db_url = Utility.environment['database']["url"] + pytest.db_url = db_url + connect(**Utility.mongoengine_connection(Utility.environment['database']["url"])) + + @pytest.mark.asyncio + async def test_valid_order_message_request(self): + from kairon.chat.handlers.channels.whatsapp import Whatsapp, WhatsappBot + with patch.object(WhatsappBot, "mark_as_read"): + with patch.object(Whatsapp, "process_message") as mock_message: + mock_message.return_value = "Hi, How may i help you!" + channel_config = { + "connector_type": "whatsapp", + "config": { + "app_secret": "jagbd34567890", + "access_token": "ERTYUIEFDGHGFHJKLFGHJKGHJ", + "verify_token": "valid", + "phone_number": "1234567890", + } + } + + bot = "whatsapp_test" + payload = { + "object": "whatsapp_business_account", + "entry": [ + { + "id": "108103872212677", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "919876543210", + "phone_number_id": "108578266683441", + }, + "contacts": [ + { + "profile": {"name": "Hitesh"}, + "wa_id": "919876543210", + } + ], + "messages": [ + { + "from": "919876543210", + "id": "wamid.HBgMOTE5NjU3DMU1MDIyQFIAEhggNzg5MEYwNEIyNDA1Q0IxMzU2QkI0NDc3RTVGMzYxNUEA", + "timestamp": "1691598412", + "type": "order", + "order": { + "catalog_id": "538971028364699", + "product_items": [ + { + "product_retailer_id": "akuba13e44", + "quantity": 1, + "item_price": 200, + "currency": "INR", + }, + { + "product_retailer_id": "0z10aj0bmq", + "quantity": 1, + "item_price": 600, + "currency": "INR", + }, + ], + }, + } + ], + }, + "field": "messages", + } + ], + } + ], + } + + handler = Whatsapp(channel_config) + await handler.handle_meta_payload(payload, + {"channel_type": "whatsapp", "bsp_type": ",meta", "tabname": "default"}, + bot) + args, kwargs = mock_message.call_args + + assert args[0] == bot + user_message = args[1] + + assert user_message.text == '/k_order_msg{"order": {"catalog_id": "538971028364699", "product_items": [{"product_retailer_id": "akuba13e44", "quantity": 1, "item_price": 200, "currency": "INR"}, {"product_retailer_id": "0z10aj0bmq", "quantity": 1, "item_price": 600, "currency": "INR"}]}}' + + @pytest.mark.asyncio + async def test_valid_attachment_message_request(self): + from kairon.chat.handlers.channels.whatsapp import Whatsapp, WhatsappBot + with patch.object(WhatsappBot, "mark_as_read"): + with patch.object(Whatsapp, "process_message") as mock_message: + mock_message.return_value = "Hi, How may i help you!" + channel_config = { + "connector_type": "whatsapp", + "config": { + "app_secret": "jagbd34567890", + "access_token": "ERTYUIEFDGHGFHJKLFGHJKGHJ", + "verify_token": "valid", + "phone_number": "1234567890", + } + } + + bot = "whatsapp_test" + payload = { + "object": "whatsapp_business_account", + "entry": [ + { + "id": "WHATSAPP_BUSINESS_ACCOUNT_ID", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "910123456789", + "phone_number_id": "12345678", + }, + "contacts": [ + { + "profile": {"name": "udit"}, + "wa_id": "wa-123456789", + } + ], + "messages": [ + { + "from": "910123456789", + "id": "wappmsg.ID", + "timestamp": "21-09-2022 12:05:00", + "document": {"id": "sdfghj567"}, + "type": "document", + } + ], + }, + "field": "messages", + } + ], + } + ], + } + + handler = Whatsapp(channel_config) + await handler.handle_meta_payload(payload, + {"channel_type": "whatsapp", "bsp_type": ",meta", "tabname": "default"}, + bot) + args, kwargs = mock_message.call_args + + assert args[0] == bot + user_message = args[1] + + assert user_message.text == '/k_multimedia_msg{"document": "sdfghj567"}' + + @pytest.mark.asyncio + async def test_payment_message_request(self): + from kairon.chat.handlers.channels.whatsapp import Whatsapp, WhatsappBot + from kairon.shared.chat.data_objects import ChannelLogs + + with patch.object(WhatsappBot, "mark_as_read"): + with patch.object(Whatsapp, "process_message") as mock_message: + mock_message.return_value = "Hi, How may i help you!" + channel_config = { + "connector_type": "whatsapp", + "config": { + "app_secret": "jagbd34567890", + "access_token": "ERTYUIEFDGHGFHJKLFGHJKGHJ", + "verify_token": "valid", + "phone_number": "1234567890", + } + } + + bot = "whatsapp_test" + payload = { + "object": "whatsapp_business_account", + "entry": [ + { + "id": "190133580861200", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "918657459321", + "phone_number_id": "257191390803220", + }, + "statuses": [ + { + "id": "wamid.HBgMOTE5NTR1OTkxNjg1FQIAEhgKNDBzOTkxOTI5NgA=", + "status": "captured", + "timestamp": "1724764153", + "recipient_id": "919515991234", + "type": "payment", + "payment": { + "reference_id": "BM3-43D-12", + "amount": {"value": 100, "offset": 100}, + "currency": "INR", + "transaction": { + "id": "order_Ovpn6PVVFYbmK3", + "type": "razorpay", + "status": "success", + "created_timestamp": 1724764153, + "updated_timestamp": 1724764153, + "amount": {"value": 100, "offset": 100}, + "currency": "INR", + "method": {"type": "upi"}, + }, + "receipt": "receipt-value", + "notes": {"key1": "value1", "key2": "value2"}, + }, + } + ], + }, + "field": "messages", + } + ], + } + ], + } + + handler = Whatsapp(channel_config) + await handler.handle_meta_payload(payload, + {"channel_type": "whatsapp", "bsp_type": ",meta", "tabname": "default"}, + bot) + args, kwargs = mock_message.call_args + + assert args[0] == bot + user_message = args[1] + + assert user_message.text == '/k_payment_msg{"payment": {"reference_id": "BM3-43D-12", "amount": {"value": 100, "offset": 100}, "currency": "INR", "transaction": {"id": "order_Ovpn6PVVFYbmK3", "type": "razorpay", "status": "success", "created_timestamp": 1724764153, "updated_timestamp": 1724764153, "amount": {"value": 100, "offset": 100}, "currency": "INR", "method": {"type": "upi"}}, "receipt": "receipt-value", "notes": {"key1": "value1", "key2": "value2"}}}' + + log = ( + ChannelLogs.objects( + bot=bot, + message_id="wamid.HBgMOTE5NTR1OTkxNjg1FQIAEhgKNDBzOTkxOTI5NgA=", + ) + .get() + .to_mongo() + .to_dict() + ) + assert log["data"] == { + 'reference_id': 'BM3-43D-12', + 'amount': {'value': 100, 'offset': 100}, + 'currency': 'INR', + 'transaction': { + 'id': 'order_Ovpn6PVVFYbmK3', + 'type': 'razorpay', + 'status': 'success', + 'created_timestamp': 1724764153, + 'updated_timestamp': 1724764153, + 'amount': {'value': 100, 'offset': 100}, + 'currency': 'INR', + 'method': {'type': 'upi'} + }, + 'receipt': 'receipt-value', + 'notes': { + 'key1': 'value1', + 'key2': 'value2' + } + } + assert log["status"] == "captured" + assert log["recipient"] == "919515991234" \ No newline at end of file