Skip to content

Commit

Permalink
log webhook response (#1069)
Browse files Browse the repository at this point in the history
* Added changes to log whatsapp webhook response in db.

* Added test cases to fix coverage issue.

* Added test cases to fix coverage issue.

* Added test cases to fix coverage issue.

* Added test cases in the chat_service_test.

* Added test cases in the chat_service_test.

---------

Co-authored-by: Mahesh <[email protected]>
  • Loading branch information
maheshsattala and Mahesh authored Nov 1, 2023
1 parent d378aa7 commit a745820
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 2 deletions.
5 changes: 5 additions & 0 deletions kairon/chat/handlers/channels/whatsapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ async def __handle_meta_payload(self, payload: Dict, metadata: Optional[Dict[Tex
msg_metadata = changes.get("value", {}).get("metadata", {})
metadata.update(msg_metadata)
messages = changes.get("value", {}).get("messages")
if not messages:
statuses = changes.get("value", {}).get("statuses")
user = metadata.get('display_phone_number')
for status_data in statuses:
ChatDataProcessor.save_whatsapp_audit_log(status_data, bot, user)
for message in messages or []:
await self.message(message, metadata, bot)

Expand Down
13 changes: 13 additions & 0 deletions kairon/shared/chat/data_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from kairon.shared.data.audit.data_objects import Auditlog
from kairon.shared.data.signals import push_notification, auditlogger
from kairon.shared.utils import Utility
from mongoengine import ListField


@auditlogger.log
Expand Down Expand Up @@ -36,3 +37,15 @@ def validate(self, clean=True):
})
Utility.register_telegram_webhook(Utility.decrypt_message(self.config['access_token']), webhook_url)


@auditlogger.log
@push_notification.apply
class WhatsappAuditLog(Auditlog):
data = DictField(default=None)
status = StringField(required=True)
message_id = StringField(required=True)
errors = ListField(DictField(default=[]))
initiator = StringField(default=None)
bot = StringField(required=True)
user = StringField(required=True)
timestamp = DateTimeField(default=datetime.utcnow)
20 changes: 19 additions & 1 deletion kairon/shared/chat/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from mongoengine import DoesNotExist
from loguru import logger
from .data_objects import Channels
from .data_objects import Channels, WhatsappAuditLog
from datetime import datetime
from kairon.shared.utils import Utility
from ..constants import ChannelTypes
Expand Down Expand Up @@ -125,3 +125,21 @@ def get_channel_endpoint(connector_type: Text, bot: Text):
except DoesNotExist:
raise AppException('Channel not configured')

@staticmethod
def save_whatsapp_audit_log(status_data: Dict, bot: Text, user: Text):
"""
save or updates channel configuration
:param status_data: status_data dict
:param bot: bot id
:param user: user id
:return: None
"""
WhatsappAuditLog(
status=status_data.get('status'),
data=status_data.get('conversation'),
initiator=status_data.get('conversation', {}).get('origin', {}).get('type'),
message_id=status_data.get('id'),
errors=status_data.get('errors', []),
bot=bot,
user=user
).save()
253 changes: 252 additions & 1 deletion tests/integration_test/chat_service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from kairon.shared.auth import Authentication
from kairon.shared.chat.processor import ChatDataProcessor
from kairon.shared.constants import UserActivityType
from kairon.shared.concurrency.actors.factory import ActorFactory
from kairon.shared.data.constant import INTEGRATION_STATUS
from kairon.shared.data.constant import TOKEN_TYPE
from kairon.shared.data.data_objects import BotSettings
Expand Down Expand Up @@ -1415,6 +1414,258 @@ def _mock_validate_hub_signature(*args, **kwargs):
assert whatsapp_msg_handler.call_args[0][4] == bot


@responses.activate
def test_whatsapp_valid_statuses_with_sent_request():
from kairon.shared.chat.data_objects import WhatsappAuditLog

def _mock_validate_hub_signature(*args, **kwargs):
return True

with patch.object(MessengerHandler, "validate_hub_signature", _mock_validate_hub_signature):
response = client.post(
f"/api/bot/whatsapp/{bot}/{token}",
headers={"hub.verify_token": "valid"},
json={
"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"
}],
"statuses": [{
"id": "wamid.HBgLMTIxMTU1NTc5NDcVAgARGBIyRkQxREUxRDJFQUJGMkQ3NDIA",
"recipient_id": "91551234567",
"status": "sent",
"timestamp": "1691548112",
"conversation": {
"id": "CONVERSATION_ID",
"expiration_timestamp": "1691598412",
"origin": {
"type": "business_initated"
}
},
"pricing": {
"pricing_model": "CBP",
"billable": "True",
"category": "business_initated"
}
}]
},
"field": "messages"
}]
}]
})
actual = response.json()
assert actual == 'success'
log = WhatsappAuditLog.objects(
bot=bot, message_id='wamid.HBgLMTIxMTU1NTc5NDcVAgARGBIyRkQxREUxRDJFQUJGMkQ3NDIA').get().to_mongo().to_dict()
assert log['data'] == {
'id': 'CONVERSATION_ID', 'expiration_timestamp': '1691598412', 'origin': {'type': 'business_initated'}
}
assert log['initiator'] == 'business_initated'
assert log['status'] == 'sent'


@responses.activate
def test_whatsapp_valid_statuses_with_delivered_request():
from kairon.shared.chat.data_objects import WhatsappAuditLog

def _mock_validate_hub_signature(*args, **kwargs):
return True

with patch.object(MessengerHandler, "validate_hub_signature", _mock_validate_hub_signature):
response = client.post(
f"/api/bot/whatsapp/{bot}/{token}",
headers={"hub.verify_token": "valid"},
json={
"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"
}],
"statuses": [{
"id": "wamid.HBgLMTIxMTU1NTc5NDcVAgARGBIyRkQxREUxRDJFQUJGMkQ3NDIB",
"recipient_id": "91551234567",
"status": "delivered",
"timestamp": "1691548112",
"conversation": {
"id": "CONVERSATION_ID",
"expiration_timestamp": "1691598412",
"origin": {
"type": "user_initiated"
}
},
"pricing": {
"pricing_model": "CBP",
"billable": "True",
"category": "service"
}
}]
},
"field": "messages"
}]
}]
})
actual = response.json()
assert actual == 'success'
log = WhatsappAuditLog.objects(
bot=bot, message_id='wamid.HBgLMTIxMTU1NTc5NDcVAgARGBIyRkQxREUxRDJFQUJGMkQ3NDIB').get().to_mongo().to_dict()
assert log['data'] == {
'id': 'CONVERSATION_ID', 'expiration_timestamp': '1691598412', 'origin': {'type': 'user_initiated'}
}
assert log['initiator'] == 'user_initiated'
assert log['status'] == 'delivered'


@responses.activate
def test_whatsapp_valid_statuses_with_read_request():
from kairon.shared.chat.data_objects import WhatsappAuditLog

def _mock_validate_hub_signature(*args, **kwargs):
return True

with patch.object(MessengerHandler, "validate_hub_signature", _mock_validate_hub_signature):
response = client.post(
f"/api/bot/whatsapp/{bot}/{token}",
headers={"hub.verify_token": "valid"},
json={
"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"
}],
"statuses": [{
"id": "wamid.HBgLMTIxMTU1NTc5NDcVAgARGBIyRkQxREUxRDJFQUJGMkQ3NDIC",
"recipient_id": "91551234567",
"status": "read",
"timestamp": "1691548112"
}]
},
"field": "messages"
}]
}]
})
actual = response.json()
assert actual == 'success'
log = WhatsappAuditLog.objects(
bot=bot, message_id='wamid.HBgLMTIxMTU1NTc5NDcVAgARGBIyRkQxREUxRDJFQUJGMkQ3NDIC').get().to_mongo().to_dict()
assert log.get('data') is None
assert log.get('initiator') is None
assert log.get('status') == 'read'

logs = WhatsappAuditLog.objects(bot=bot, user='919876543210')
assert len(WhatsappAuditLog.objects(bot=bot, user='919876543210')) == 3
assert logs[0]['data'] == {
'id': 'CONVERSATION_ID', 'expiration_timestamp': '1691598412', 'origin': {'type': 'business_initated'}
}
assert logs[0]['initiator'] == 'business_initated'
assert logs[0]['status'] == 'sent'
assert logs[1]['data'] == {
'id': 'CONVERSATION_ID', 'expiration_timestamp': '1691598412', 'origin': {'type': 'user_initiated'}
}
assert logs[1]['initiator'] == 'user_initiated'
assert logs[1]['status'] == 'delivered'
assert logs[2]['status'] == 'read'


@responses.activate
def test_whatsapp_valid_statuses_with_errors_request():
from kairon.shared.chat.data_objects import WhatsappAuditLog

def _mock_validate_hub_signature(*args, **kwargs):
return True

with patch.object(MessengerHandler, "validate_hub_signature", _mock_validate_hub_signature):
response = client.post(
f"/api/bot/whatsapp/{bot}/{token}",
headers={"hub.verify_token": "valid"},
json={
"object": "whatsapp_business_account",
"entry": [{
"id": "108103872212677",
"changes": [{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "919876543219",
"phone_number_id": "108578266683441"
},
"contacts": [{
"profile": {
"name": "Hitesh"
},
"wa_id": "919876543210"
}],
"statuses": [
{
"id": "wamid.HBgLMTIxMTU1NTc5NDcVAgARGBIyRkQxREUxRDJFQUJGMkQ3NDIZ",
"status": "failed",
"timestamp": "1689380458",
"recipient_id": "15551234567",
"errors": [
{
"code": 130472,
"title": "User's number is part of an experiment",
"message": "User's number is part of an experiment",
"error_data": {
"details": "Failed to send message because this user's phone number is part of an experiment"
},
"href": "https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/"
}
]
}
]
},
"field": "messages"
}]
}]
})
actual = response.json()
assert actual == 'success'
assert WhatsappAuditLog.objects(bot=bot, message_id='wamid.HBgLMTIxMTU1NTc5NDcVAgARGBIyRkQxREUxRDJFQUJGMkQ3NDIZ')
log = WhatsappAuditLog.objects(bot=bot, user='919876543219').get().to_mongo().to_dict()
assert log.get('status') == 'failed'
assert log.get('data') is None
assert log.get('errors') == [{
'code': 130472, 'title': "User's number is part of an experiment",
'message': "User's number is part of an experiment",
'error_data': {'details': "Failed to send message because this user's phone number is part of an experiment"},
'href': 'https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/'
}]


@responses.activate
def test_whatsapp_valid_unsupported_message_request():
def _mock_validate_hub_signature(*args, **kwargs):
Expand Down

0 comments on commit a745820

Please sign in to comment.