From bdfa6ad5ddeb7c1179d4c6c7474d3b2b8049ec28 Mon Sep 17 00:00:00 2001 From: Nupur Khare <78532321+nupur-khare@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:38:29 +0530 Subject: [PATCH] 360Dialog Whatsapp Templates (#1090) * 1. Added APIs to create, delete and update whatsapp templates. 2. Added unit and integration test cases for the same. * Fixed requested changes. * Fixed requested changes. --------- Co-authored-by: Nupur Khare --- kairon/api/app/routers/bot/channels.py | 56 ++- kairon/shared/channels/whatsapp/bsp/base.py | 12 + .../shared/channels/whatsapp/bsp/dialog360.py | 60 ++- kairon/shared/constants.py | 1 + kairon/shared/utils.py | 15 +- tests/integration_test/chat_service_test.py | 1 + tests/integration_test/services_test.py | 218 +++++++++++ tests/unit_test/channels/bsp_test.py | 352 ++++++++++++++++++ 8 files changed, 707 insertions(+), 8 deletions(-) diff --git a/kairon/api/app/routers/bot/channels.py b/kairon/api/app/routers/bot/channels.py index ccd02efb5..86710fe09 100644 --- a/kairon/api/app/routers/bot/channels.py +++ b/kairon/api/app/routers/bot/channels.py @@ -2,18 +2,18 @@ from starlette.requests import Request from kairon import Utility -from kairon.events.definitions.message_broadcast import MessageBroadcastEvent -from kairon.shared.auth import Authentication from kairon.api.models import ( - Response, + Response, DictData, ) +from kairon.events.definitions.message_broadcast import MessageBroadcastEvent +from kairon.shared.auth import Authentication from kairon.shared.channels.whatsapp.bsp.factory import BusinessServiceProviderFactory -from kairon.shared.chat.models import ChannelRequest, MessageBroadcastRequest from kairon.shared.chat.broadcast.processor import MessageBroadcastProcessor +from kairon.shared.chat.models import ChannelRequest, MessageBroadcastRequest from kairon.shared.chat.processor import ChatDataProcessor from kairon.shared.constants import TESTER_ACCESS, DESIGNER_ACCESS, WhatsappBSPTypes, EventRequestType -from kairon.shared.models import User from kairon.shared.data.processor import MongoProcessor +from kairon.shared.models import User router = APIRouter() mongo_processor = MongoProcessor() @@ -107,6 +107,52 @@ async def initiate_platform_onboarding( return Response(message='Channel added', data=channel_endpoint) +@router.post("/whatsapp/templates/{bsp_type}", response_model=Response) +async def add_message_templates( + request_data: DictData, + bsp_type: str = Path(default=None, description="Business service provider type", + example=WhatsappBSPTypes.bsp_360dialog.value), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Adds message templates for configured bsp account. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.add_template(request_data.data, current_user.get_bot(), current_user.get_user()) + return Response(data=response) + + +@router.put("/whatsapp/templates/{bsp_type}/{template_id}", response_model=Response) +async def edit_message_templates( + request_data: DictData, + template_id: str = Path(default=None, description="template id", example="594425479261596"), + bsp_type: str = Path(default=None, description="Business service provider type", + example=WhatsappBSPTypes.bsp_360dialog.value), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Edits message templates for configured bsp account. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.edit_template(request_data.data, template_id), + return Response(data=response) + + +@router.delete("/whatsapp/templates/{bsp_type}/{template_id}", response_model=Response) +async def delete_message_templates( + template_id: str = Path(default=None, description="template id", example="594425479261596"), + bsp_type: str = Path(default=None, description="Business service provider type", + example=WhatsappBSPTypes.bsp_360dialog.value), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Deletes message templates for configured bsp account. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.delete_template(template_id) + return Response(data=response) + + @router.get("/whatsapp/templates/{bsp_type}/list", response_model=Response) async def retrieve_message_templates( request: Request, diff --git a/kairon/shared/channels/whatsapp/bsp/base.py b/kairon/shared/channels/whatsapp/bsp/base.py index c059e26db..4be9093ec 100644 --- a/kairon/shared/channels/whatsapp/bsp/base.py +++ b/kairon/shared/channels/whatsapp/bsp/base.py @@ -15,6 +15,18 @@ def post_process(self, **kwargs): def save_channel_config(self, **kwargs): raise NotImplementedError("Provider not implemented") + @abstractmethod + def add_template(self, **kwargs): + raise NotImplementedError("Provider not implemented") + + @abstractmethod + def edit_template(self, **kwargs): + raise NotImplementedError("Provider not implemented") + + @abstractmethod + def delete_template(self, **kwargs): + raise NotImplementedError("Provider not implemented") + @abstractmethod def get_template(self, **kwargs): raise NotImplementedError("Provider not implemented") diff --git a/kairon/shared/channels/whatsapp/bsp/dialog360.py b/kairon/shared/channels/whatsapp/bsp/dialog360.py index e5437225d..61beb23f0 100644 --- a/kairon/shared/channels/whatsapp/bsp/dialog360.py +++ b/kairon/shared/channels/whatsapp/bsp/dialog360.py @@ -1,13 +1,15 @@ import ast -from typing import Text +from typing import Text, Dict + from loguru import logger from mongoengine import DoesNotExist from kairon import Utility from kairon.exceptions import AppException +from kairon.shared.account.activity_log import UserActivityLogger from kairon.shared.channels.whatsapp.bsp.base import WhatsappBusinessServiceProviderBase from kairon.shared.chat.processor import ChatDataProcessor -from kairon.shared.constants import WhatsappBSPTypes, ChannelTypes +from kairon.shared.constants import WhatsappBSPTypes, ChannelTypes, UserActivityType class BSP360Dialog(WhatsappBusinessServiceProviderBase): @@ -80,6 +82,60 @@ def save_channel_config(self, clientId: Text, client: Text, channels: list, part } return ChatDataProcessor.save_channel_config(conf, self.bot, self.user) + def add_template(self, data: Dict, bot: Text, user: Text): + try: + Utility.validate_create_template_request(data) + config = ChatDataProcessor.get_channel_config(ChannelTypes.WHATSAPP.value, self.bot, mask_characters=False) + partner_id = Utility.environment["channels"]["360dialog"]["partner_id"] + channel_id = config.get("config", {}).get("channel_id") + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + template_endpoint = f'/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates' + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{template_endpoint}" + resp = Utility.execute_http_request(request_method="POST", http_url=url, request_body=data, headers=headers, + validate_status=True, err_msg="Failed to add template: ") + UserActivityLogger.add_log(a_type=UserActivityType.template_creation.value, email=user, bot=bot, message=['Template created!']) + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + + def edit_template(self, data: Dict, template_id: str): + try: + Utility.validate_edit_template_request(data) + config = ChatDataProcessor.get_channel_config(ChannelTypes.WHATSAPP.value, self.bot, mask_characters=False) + partner_id = Utility.environment["channels"]["360dialog"]["partner_id"] + channel_id = config.get("config", {}).get("channel_id") + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + template_endpoint = f'/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates/{template_id}' + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{template_endpoint}" + resp = Utility.execute_http_request(request_method="PATCH", http_url=url, request_body=data, headers=headers, + validate_status=True, err_msg="Failed to edit template: ") + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + except Exception as e: + logger.exception(e) + raise AppException(str(e)) + + def delete_template(self, template_id: str): + try: + config = ChatDataProcessor.get_channel_config(ChannelTypes.WHATSAPP.value, self.bot, mask_characters=False) + partner_id = Utility.environment["channels"]["360dialog"]["partner_id"] + channel_id = config.get("config", {}).get("channel_id") + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + template_endpoint = f'/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates/{template_id}' + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{template_endpoint}" + resp = Utility.execute_http_request(request_method="DELETE", http_url=url, headers=headers, + validate_status=True, err_msg="Failed to delete template: ") + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + def get_template(self, template_id: Text): return self.list_templates(id=template_id) diff --git a/kairon/shared/constants.py b/kairon/shared/constants.py index 047bf208d..6fa94a09d 100644 --- a/kairon/shared/constants.py +++ b/kairon/shared/constants.py @@ -57,6 +57,7 @@ class UserActivityType(str, Enum): login = 'login' login_refresh_token = "login_refresh_token" invalid_login = 'invalid_login' + template_creation = 'template_creation' class EventClass(str, Enum): diff --git a/kairon/shared/utils.py b/kairon/shared/utils.py index 948d2fe2b..6acb4425b 100644 --- a/kairon/shared/utils.py +++ b/kairon/shared/utils.py @@ -1065,6 +1065,19 @@ def reload_model(bot: Text, email: Text): else: raise AppException("Agent config not found!") + @staticmethod + def validate_create_template_request(data: Dict): + required_keys = ['name', 'category', 'components', 'language'] + missing_keys = [key for key in required_keys if key not in data] + if missing_keys: + raise AppException(f'Missing {", ".join(missing_keys)} in request body!') + + @staticmethod + def validate_edit_template_request(data: Dict): + non_editable_keys = ['name', 'category', 'language'] + if any(key in data for key in non_editable_keys): + raise AppException('Only "components" and "allow_category_change" fields can be edited!') + @staticmethod def initiate_fastapi_apm_client(): from elasticapm.contrib.starlette import make_apm_client @@ -1402,7 +1415,7 @@ def execute_http_request( response = requests.request( request_method.upper(), http_url, params=request_body, headers=headers, timeout=kwargs.get('timeout') ) - elif request_method.lower() in ['post', 'put']: + elif request_method.lower() in ['post', 'put', 'patch']: response = session.request( request_method.upper(), http_url, json=request_body, headers=headers, timeout=kwargs.get('timeout') ) diff --git a/tests/integration_test/chat_service_test.py b/tests/integration_test/chat_service_test.py index 53d8a81cd..6d58ef30b 100644 --- a/tests/integration_test/chat_service_test.py +++ b/tests/integration_test/chat_service_test.py @@ -1341,6 +1341,7 @@ def _mock_validate_hub_signature(*args, **kwargs): @responses.activate def test_whatsapp_valid_order_message_request(): + responses.reset() def _mock_validate_hub_signature(*args, **kwargs): return True diff --git a/tests/integration_test/services_test.py b/tests/integration_test/services_test.py index 4573e57c5..436b7a677 100644 --- a/tests/integration_test/services_test.py +++ b/tests/integration_test/services_test.py @@ -13942,6 +13942,107 @@ def test_add_channel_config(monkeypatch): assert actual["data"].startswith(f"http://localhost:5056/api/bot/slack/{pytest.bot}/e") +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_add_template_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + data = { + "name": "Introduction template", + "category": "UTILITY", + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. We will process your request get back to you shortly", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "language": "es_ES", + } + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/templates/360dialog", + json={'data': data}, + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + print(actual) + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] == None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_edit_template_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + data = { + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. Let us know your queries!", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "allow_category_change": False + } + response = client.put( + f"/api/bot/{pytest.bot}/channels/whatsapp/templates/360dialog/test_one_id", + json={'data': data}, + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + print(actual) + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] == None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_delete_template_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + response = client.delete( + f"/api/bot/{pytest.bot}/channels/whatsapp/templates/360dialog/test_one_id", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + print(actual) + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] == None + + def test_list_whatsapp_templates_error(): response = client.get( f"/api/bot/{pytest.bot}/channels/whatsapp/templates/360dialog/list", @@ -14057,6 +14158,123 @@ def _mock_get_bot_settings(*args, **kwargs): assert actual["data"].startswith(f"http://kairon-api.digite.com/api/bot/whatsapp/{pytest.bot}/e") +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.add_template", autospec=True) +def test_add_template(mock_add_template): + data = { + "name": "Introduction template", + "category": "MARKETING", + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. We will process your request get back to you shortly", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "language": "es_ES", + "allow_category_change": True + } + api_resp = { + "id": "594425479261596", + "status": "PENDING", + "category": "MARKETING" + } + mock_add_template.return_value = api_resp + + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/templates/360dialog", + json={'data': data}, + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + print(actual) + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == {'id': '594425479261596', 'status': 'PENDING', 'category': 'MARKETING'} + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.edit_template", autospec=True) +def test_edit_template(mock_edit_template): + data = { + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. Let us know your queries!", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "allow_category_change": False + } + api_resp = { + "success": True + } + mock_edit_template.return_value = api_resp + + response = client.put( + f"/api/bot/{pytest.bot}/channels/whatsapp/templates/360dialog/test_id", + json={'data': data}, + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + print(actual) + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == [{'success': True}] + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.delete_template", autospec=True) +def test_delete_template(mock_delete_template): + api_resp = { + "meta": { + "developer_message": "template name=Introduction template was deleted", + "http_code": 200, + "success": True + } + } + mock_delete_template.return_value = api_resp + + response = client.delete( + f"/api/bot/{pytest.bot}/channels/whatsapp/templates/360dialog/test_id", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + print(actual) + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == api_resp + + @patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.list_templates", autospec=True) def test_list_templates(mock_list_templates): api_resp = { diff --git a/tests/unit_test/channels/bsp_test.py b/tests/unit_test/channels/bsp_test.py index dbf3989c8..b73c64670 100644 --- a/tests/unit_test/channels/bsp_test.py +++ b/tests/unit_test/channels/bsp_test.py @@ -1,4 +1,6 @@ import os +from unittest import mock + import pytest import responses from mongoengine import connect, ValidationError @@ -10,6 +12,7 @@ from kairon.shared.channels.whatsapp.bsp.factory import BusinessServiceProviderFactory from kairon.shared.chat.processor import ChatDataProcessor from kairon.shared.constants import WhatsappBSPTypes, ChannelTypes +from kairon.shared.data.audit.data_objects import AuditLogData from kairon.shared.data.data_objects import BotSettings from kairon.shared.data.processor import MongoProcessor from kairon.shared.utils import Utility @@ -288,6 +291,346 @@ def _mock_get_bot_settings(*args, **kwargs): 'partner_id': partner_id, 'bsp_type': '360dialog', 'api_key': 'kHCwksdsdsMVYVx0doabaDyRLUQJUAK', 'waba_account_id': 'Cyih7GWA'} + @responses.activate + def test_add_template(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + responses.reset() + bot = "62bc24b493a0d6b7a46328ff" + partner_id = "new_partner_id" + channel_id = "dfghjkl" + data = { + "name": "Introduction template", + "category": "MARKETING", + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. We will process your request get back to you shortly", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "language": "es_ES", + "allow_category_change": True + } + api_resp = { + "id": "594425479261596", + "status": "PENDING", + "category": "MARKETING" + } + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + url = f"{base_url}/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates" + responses.add("POST", json=api_resp, url=url) + template = BSP360Dialog(bot, "test").add_template(data, bot, "test") + assert template == {'category': 'MARKETING', 'id': '594425479261596', 'status': 'PENDING'} + count = AuditLogData.objects(attributes=[{"key": "bot", "value": bot}], user="test", action="activity").count() + assert count == 1 + + @responses.activate + def test_add_template_with_missing_keys(self): + bot = "62bc24b493a0d6b7a46328ff" + data = { + "name": "Introduction template", + "category": "UTILITY", + "language": "es_ES", + "allow_category_change": True + } + with pytest.raises(AppException, match="Missing components in request body!"): + BSP360Dialog(bot, "test").add_template(data, bot, "test") + + def test_add_template_error(self, monkeypatch): + bot = "62bc24b493a0d6b7a46328fg" + data = { + "name": "Introduction template", + "category": "UTILITY", + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. We will process your request get back to you shortly", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "language": "es_ES", + } + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").add_template(data, bot, "user") + + @responses.activate + def test_add_template_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + partner_id = "new_partner_id" + channel_id = "dfghjkl" + data = { + "name": "Introduction template", + "category": "MARKETING", + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. We will process your request get back to you shortly", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "language": "es_ES", + "allow_category_change": True + } + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + url = f"{base_url}/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates" + responses.add("POST", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to add template: *"): + BSP360Dialog(bot, "user").add_template(data, bot, "user") + + @responses.activate + def test_edit_template(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + template_id = "test_id" + partner_id = "new_partner_id" + channel_id = "dfghjkl" + data = { + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. Let us know your queries!", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "allow_category_change": False + } + api_resp = { + "success": True + } + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + url = f"{base_url}/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates/{template_id}" + responses.add("PATCH", json=api_resp, url=url) + template = BSP360Dialog(bot, "test").edit_template(data, template_id) + assert template == {'success': True} + + @responses.activate + def test_edit_template_with_non_editable_keys(self): + bot = "62bc24b493a0d6b7a46328ff" + template_id = "test_id" + partner_id = "new_partner_id" + channel_id = "dfghjkl" + data = { + "name": "Introduction template", + "category": "UTILITY", + "language": "es_ES", + } + with pytest.raises(AppException, match='Only "components" and "allow_category_change" fields can be edited!'): + BSP360Dialog(bot, "test").edit_template(data, template_id) + + @responses.activate + def test_edit_template_channel_not_found(self, monkeypatch): + bot = "62bc24b493a0d6b7a46328fg" + template_id = "test_id" + data = { + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. Let us know your queries!", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "allow_category_change": False + } + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").edit_template(data, template_id) + + @responses.activate + def test_edit_template_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + template_id = "test_id" + partner_id = "new_partner_id" + channel_id = "dfghjkl" + data = { + "components": [ + { + "format": "TEXT", + "text": "New request", + "type": "HEADER" + }, + { + "type": "BODY", + "text": "Hi {{1}}, thanks for getting in touch with {{2}}. Let us know your queries!", + "example": { + "body_text": [ + [ + "Nupur", + "360dialog" + ] + ] + } + }, + { + "text": "WhatsApp Business API provided by 360dialog", + "type": "FOOTER" + } + ], + "allow_category_change": True + } + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + url = f"{base_url}/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates/{template_id}" + responses.add("PATCH", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to edit template: Internal Server Error"): + BSP360Dialog(bot, "user").edit_template(data, template_id) + + @responses.activate + def test_delete_template(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + template_id = "test_id" + partner_id = "new_partner_id" + channel_id = "dfghjkl" + api_resp = { + "meta": { + "developer_message": "template name=Introduction template was deleted", + "http_code": 200, + "success": True + } + } + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + url = f"{base_url}/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates/{template_id}" + responses.add("DELETE", json=api_resp, url=url) + template = BSP360Dialog(bot, "test").delete_template(template_id) + assert template == {'meta': {'developer_message': 'template name=Introduction template was deleted', 'http_code': 200, 'success': True}} + + @responses.activate + def test_delete_template_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + template_id = "test_id" + partner_id = "new_partner_id" + channel_id = "dfghjkl" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + url = f"{base_url}/v1/partners/{partner_id}/waba_accounts/{channel_id}/waba_templates/{template_id}" + responses.add("DELETE", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to delete template: *"): + BSP360Dialog(bot, "user").delete_template(template_id) + + def test_delete_template_error(self, monkeypatch): + bot = "62bc24b493a0d6b7a46328fg" + template_id = "test_id" + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").delete_template(template_id) + @responses.activate def test_get_template(self, monkeypatch): bot = "62bc24b493a0d6b7a46328ff" @@ -468,3 +811,12 @@ def test_parent_class_abstract_methods(self): with pytest.raises(Exception): WhatsappBusinessServiceProviderBase().post_process() + + with pytest.raises(Exception): + WhatsappBusinessServiceProviderBase.add_template() + + with pytest.raises(Exception): + WhatsappBusinessServiceProviderBase.edit_template() + + with pytest.raises(Exception): + WhatsappBusinessServiceProviderBase.delete_template()