Skip to content

Commit

Permalink
Made review changes and added test cases.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mahesh committed Dec 12, 2023
1 parent 7ea8bd6 commit 4c605f0
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,16 @@
from kairon.chat.agent_processor import AgentProcessor


path_to_service_account_key = 'kairon/chat/handlers/channels/business_messages/service_account_key.json'


class BusinessMessagesHandler(InputChannel):

def __init__(self, bot: Text, user: User, request: Request):
self.bot = bot
self.user = user
self.request = request
self.channel_config = {}

async def validate(self):
business_message_conf = ChatDataProcessor.get_channel_config("business_messages", self.bot,
mask_characters=False)

partner_key = business_message_conf["config"]["private_key_id"]
partner_key = self.channel_config["private_key_id"]
generated_signature = base64.b64encode(hmac.new(partner_key.encode(), msg=await self.request.body(),
digestmod=hashlib.sha512).digest()).decode('UTF-8')
google_signature = self.request.headers.get('x-goog-signature')
Expand All @@ -50,16 +45,10 @@ async def handle_message(self):
if 'secret' in request_body:
return request_body.get('secret')

await self.validate()
business_message_conf = ChatDataProcessor.get_channel_config("business_messages", self.bot,
mask_characters=False)
credentials_json = {
"type": "service_account",
"private_key_id": business_message_conf["config"]["private_key_id"],
"private_key": business_message_conf["config"]["private_key"],
"client_email": business_message_conf["config"]["client_email"],
"client_id": business_message_conf["config"]["client_id"]
}
self.channel_config = business_message_conf['config']
await self.validate()
print(request_body)
metadata = self.get_metadata(self.request) or {}
metadata.update({"is_integration_user": True, "bot": self.bot, "account": self.user.account,
Expand All @@ -71,10 +60,11 @@ async def handle_message(self):
message = request_body['message']['text']
conversation_id = request_body['conversationId']
message_id = request_body['message']['messageId']
business_messages = BusinessMessages(credentials_json)
business_messages = BusinessMessages(self.channel_config)
await business_messages.handle_user_message(text=message, sender_id=self.user.email, metadata=metadata,
conversation_id=conversation_id, bot=self.bot,
message_id=message_id)
logger.debug("Business Messages Request: " + str(request_body))
return {"status": "OK"}

@staticmethod
Expand All @@ -84,30 +74,32 @@ def check_message_create_time(create_time: str):
current_time = datetime.utcnow()
message_time = datetime.strptime(create_time, '%Y-%m-%dT%H:%M:%S.%fZ')
time_difference = current_time - message_time
print(time_difference)
return True if time_difference.total_seconds() < 5 else False


class BusinessMessages:

def __init__(self, credentials_json: Dict):
self.credentials_json = credentials_json
def __init__(self, channel_config: Dict):
self.channel_config = channel_config

@classmethod
def name(cls) -> Text:
return "business_messages"

@staticmethod
def write_json_to_file(json_code, file_path):
import json

with open(file_path, 'w') as json_file:
json.dump(json_code, json_file, indent=2)
def get_credentials(self):
credentials_json = {
"type": "service_account",
"private_key_id": self.channel_config["private_key_id"],
"private_key": self.channel_config["private_key"],
"client_email": self.channel_config["client_email"],
"client_id": self.channel_config["client_id"]
}
return credentials_json

def get_business_message_credentials(self):
self.write_json_to_file(self.credentials_json, path_to_service_account_key)
credentials = ServiceAccountCredentials.from_json_keyfile_name(
path_to_service_account_key,
credentials = self.get_credentials()
credentials = ServiceAccountCredentials.from_json_keyfile_dict(
credentials,
scopes=['https://www.googleapis.com/auth/businessmessages'])
return credentials

Expand All @@ -117,16 +109,14 @@ async def handle_user_message(
) -> None:
user_msg = UserMessage(text=text, message_id=message_id,
input_channel=self.name(), sender_id=sender_id, metadata=metadata)
message = "No Response"
try:
response = await self.process_message(bot, user_msg)
print(response)
message = response['response'][0]['text']
except Exception:
logger.exception(
"Exception when trying to handle webhook for business message."
)
await self.send_message(message=message, conversation_id=conversation_id)
message = response['response'][0].get('text')
except Exception as e:
raise Exception(f"Exception when trying to handle webhook for business message: {str(e)}")
if message:
await self.send_message(message=message, conversation_id=conversation_id)

@staticmethod
async def process_message(bot: str, user_message: UserMessage):
Expand All @@ -146,19 +136,14 @@ async def send_message(self, message: Text, conversation_id: Text):
credentials = self.get_business_message_credentials()
client = bm_client.BusinessmessagesV1(credentials=credentials)

create_request = BusinessmessagesConversationsEventsCreateRequest(
eventId=str(uuid.uuid4().int),
businessMessagesEvent=BusinessMessagesEvent(
representative=BusinessMessagesRepresentative(
representativeType=BusinessMessagesRepresentative.RepresentativeTypeValueValuesEnum.BOT
),
eventType=BusinessMessagesEvent.EventTypeValueValuesEnum.TYPING_STARTED
),
parent='conversations/' + conversation_id)
self.trigger_start_typing_event(conversation_id, client)

bm_client.BusinessmessagesV1.ConversationsEventsService(
client=client).Create(request=create_request)
self.send_google_business_message(message, conversation_id, client)

self.trigger_stop_typing_event(conversation_id, client)

@staticmethod
def send_google_business_message(message, conversation_id, client):
message_obj = BusinessMessagesMessage(
messageId=str(uuid.uuid4().int),
representative=BusinessMessagesRepresentative(
Expand All @@ -173,6 +158,23 @@ async def send_message(self, message: Text, conversation_id: Text):
bm_client.BusinessmessagesV1.ConversationsMessagesService(
client=client).Create(request=create_request)

@staticmethod
def trigger_start_typing_event(conversation_id, client):
create_request = BusinessmessagesConversationsEventsCreateRequest(
eventId=str(uuid.uuid4().int),
businessMessagesEvent=BusinessMessagesEvent(
representative=BusinessMessagesRepresentative(
representativeType=BusinessMessagesRepresentative.RepresentativeTypeValueValuesEnum.BOT
),
eventType=BusinessMessagesEvent.EventTypeValueValuesEnum.TYPING_STARTED
),
parent='conversations/' + conversation_id)

bm_client.BusinessmessagesV1.ConversationsEventsService(
client=client).Create(request=create_request)

@staticmethod
def trigger_stop_typing_event(conversation_id, client):
create_request = BusinessmessagesConversationsEventsCreateRequest(
eventId=str(uuid.uuid4().int),
businessMessagesEvent=BusinessMessagesEvent(
Expand Down
Empty file.

This file was deleted.

2 changes: 1 addition & 1 deletion kairon/chat/handlers/channels/factory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Text

from kairon.chat.handlers.channels.business_messages.business_messages import BusinessMessagesHandler
from kairon.chat.handlers.channels.business_messages import BusinessMessagesHandler
from kairon.chat.handlers.channels.hangouts import HangoutsHandler
from kairon.chat.handlers.channels.messenger import MessengerHandler, InstagramHandler
from kairon.chat.handlers.channels.msteams import MSTeamsHandler
Expand Down
84 changes: 81 additions & 3 deletions tests/integration_test/chat_service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,88 @@ def test_business_messages_invalid_auth():
assert actual == {"data": None, "success": False, "error_code": 401, "message": "Could not validate credentials"}


@patch("kairon.chat.handlers.channels.business_messages.business_messages.BusinessMessages.process_message")
@patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name')
def test_business_messages_with_secret():
response = client.post(
f"/api/bot/business_messages/{bot}/{token}",
headers={"Authorization": "Bearer Test"},
json={"secret": "34983948"}
)
actual = response.json()
assert actual == "34983948"


@patch("kairon.chat.handlers.channels.business_messages.BusinessMessagesHandler.check_message_create_time")
@patch("kairon.chat.handlers.channels.business_messages.BusinessMessages.process_message")
@patch('businessmessages.businessmessages_v1_client.BusinessmessagesV1')
def test_business_messages_with_exception(mock_business_messages, mock_process_message,
mock_check_message_create_time):
mock_check_message_create_time.return_value = True
mock_business_messages.return_value = {}
mock_process_message.side_effect = Exception("invalid user message")
with pytest.raises(Exception,
match="Exception when trying to handle webhook for business message: invalid user message"):
client.post(
f"/api/bot/business_messages/{bot}/{token}",
headers={"Authorization": "Bearer Test"},
json={"message": {
"name": "conversations/24ab463a-a6bf-4049-b49e-cc05fb1dc384/messages/5979C5-325C-4700-BF5A-0156C39C541",
"text": "Hello!",
"createTime": "2023-12-04T06:30:46.034290Z",
"messageId": "5979C547-325C-4700-BF5A-0156C39C5641"},
"context": {
"placeId": "",
"userInfo": {
"displayName": "Mahesh Sattala",
"userDeviceLocale": "en-IN"
},
"resolvedLocale": "en"
},
"sendTime": "2023-12-04T06:30:46.662594Z",
"conversationId": "24ab463a-a6bf-4056-b49e-aa05fb1dc384",
"requestId": "5979C547-325C-4700-BF5A-0156C45C1541",
"agent": "brands/bd7e3fe0-3c3e-4b3e-4759-6e46ac0412a5/agents/3cf91834-3b5e-4c4b-a632-9575f0cc3444"
}
)


@patch("kairon.chat.handlers.channels.business_messages.BusinessMessagesHandler.check_message_create_time")
@patch("kairon.chat.handlers.channels.business_messages.BusinessMessages.process_message")
def test_business_messages_without_message(mock_process_message, mock_check_message_create_time):
mock_check_message_create_time.return_value = True
mock_process_message.return_value = {'response': [{'text': None}]}
response = client.post(
f"/api/bot/business_messages/{bot}/{token}",
headers={"Authorization": "Bearer Test"},
json={"message": {
"name": "conversations/24ab463a-a6bf-4049-b49e-cc05fb1dc384/messages/5979C5-325C-4700-BF5A-0156C39C541",
"text": "Hello!",
"createTime": "2023-12-04T06:30:46.034290Z",
"messageId": "5979C547-325C-4700-BF5A-0156C39C5641"},
"context": {
"placeId": "",
"userInfo": {
"displayName": "Mahesh Sattala",
"userDeviceLocale": "en-IN"
},
"resolvedLocale": "en"
},
"sendTime": "2023-12-04T06:30:46.662594Z",
"conversationId": "24ab463a-a6bf-4056-b49e-aa05fb1dc384",
"requestId": "5979C547-325C-4700-BF5A-0156C45C1541",
"agent": "brands/bd7e3fe0-3c3e-4b3e-4759-6e46ac0412a5/agents/3cf91834-3b5e-4c4b-a632-9575f0cc3444"
}
)
actual = response.json()
assert actual == {"status": "OK"}


@patch("kairon.chat.handlers.channels.business_messages.BusinessMessagesHandler.check_message_create_time")
@patch("kairon.chat.handlers.channels.business_messages.BusinessMessages.process_message")
@patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_dict')
@patch('businessmessages.businessmessages_v1_client.BusinessmessagesV1')
def test_business_messages_with_valid_data(mock_business_messages, mock_credentials, mock_process_message):
def test_business_messages_with_valid_data(mock_business_messages, mock_credentials, mock_process_message,
mock_check_message_create_time):
mock_check_message_create_time.return_value = True
mock_credentials.return_value = {}
mock_business_messages.return_value = {}
mock_process_message.return_value = {'response': [{'text': 'How may I help you!'}]}
Expand Down
28 changes: 28 additions & 0 deletions tests/unit_test/chat/chat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,34 @@ def __mock_endpoint(*args):
"test",
"test")

@responses.activate
def test_save_channel_config_business_messages(self):
def __mock_endpoint(*args):
return f"https://[email protected]/api/bot/business_messages/tests/test"

with patch('kairon.shared.data.utils.DataUtility.get_channel_endpoint', __mock_endpoint):
channel_endpoint = ChatDataProcessor.save_channel_config(
{
"connector_type": "business_messages",
"config": {
"type": "service_account",
"private_key_id": "fa006e13b1e17eddf3990eede45ca6111eb74945",
"private_key": "test_private_key",
"client_email": "[email protected]",
"client_id": "102056160806575769486"
}
},
"test",
"test")
assert channel_endpoint == "https://[email protected]/api/bot/business_messages/tests/test"
business_messages_config = ChatDataProcessor.get_channel_config("business_messages",
"test")
assert business_messages_config['config'] == {'type': 'service_account',
'private_key_id': 'fa006e13b1e17eddf3990eede45ca6111eb*****',
'private_key': 'test_privat*****',
'client_email': '[email protected]*****',
'client_id': '1020561608065757*****'}

@mock.patch('kairon.shared.utils.MongoClient', autospec=True)
def test_fetch_session_history_error(self, mock_mongo):
mock_mongo.side_effect = ServerSelectionTimeoutError("Failed to retrieve conversation: Failed to connect")
Expand Down

0 comments on commit 4c605f0

Please sign in to comment.