From c7434a5bdffdeec1cf1ec74f099dc0320adff6b5 Mon Sep 17 00:00:00 2001 From: spandan_mondal Date: Sun, 8 Dec 2024 21:35:28 +0530 Subject: [PATCH] changes and test cases --- kairon/api/app/routers/bot/bot.py | 2 +- kairon/cli/mail_channel_read.py | 3 +- kairon/events/definitions/mail_channel.py | 1 - kairon/events/utility.py | 3 +- kairon/shared/channels/mail/data_objects.py | 4 +- kairon/shared/channels/mail/processor.py | 1 + tests/unit_test/channels/mail_channel_test.py | 69 +++++++++++-- .../unit_test/channels/mail_scheduler_test.py | 98 ++++++++++++++++--- 8 files changed, 151 insertions(+), 30 deletions(-) diff --git a/kairon/api/app/routers/bot/bot.py b/kairon/api/app/routers/bot/bot.py index 6fe85cb2d..318981640 100644 --- a/kairon/api/app/routers/bot/bot.py +++ b/kairon/api/app/routers/bot/bot.py @@ -1663,7 +1663,7 @@ async def get_slot_actions( @router.get("/mail_channel/logs", response_model=Response) -async def get_action_server_logs(start_idx: int = 0, page_size: int = 10, +async def get_mail_channel_logs(start_idx: int = 0, page_size: int = 10, current_user: User = Security(Authentication.get_current_user_and_bot, scopes=TESTER_ACCESS)): """ diff --git a/kairon/cli/mail_channel_read.py b/kairon/cli/mail_channel_read.py index 274bae61b..2ee345955 100644 --- a/kairon/cli/mail_channel_read.py +++ b/kairon/cli/mail_channel_read.py @@ -1,9 +1,8 @@ -import json from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter from typing import List from rasa.cli import SubParsersAction -from kairon.events.definitions.mail_channel import MailProcessEvent, MailReadEvent +from kairon.events.definitions.mail_channel import MailReadEvent def read_channel_mails(args): diff --git a/kairon/events/definitions/mail_channel.py b/kairon/events/definitions/mail_channel.py index 30d5b98cf..533a1ea0f 100644 --- a/kairon/events/definitions/mail_channel.py +++ b/kairon/events/definitions/mail_channel.py @@ -96,4 +96,3 @@ def execute(self, **kwargs): except Exception as e: raise AppException(f"Failed to schedule mail reading for bot {self.bot}. Error: {str(e)}") - is_initialized = False \ No newline at end of file diff --git a/kairon/events/utility.py b/kairon/events/utility.py index aae90e4e1..1d4f0be24 100644 --- a/kairon/events/utility.py +++ b/kairon/events/utility.py @@ -40,8 +40,9 @@ def update_job(event_type: Text, request_data: Dict, is_scheduled: bool): @staticmethod def schedule_channel_mail_reading(bot: str): + from kairon.shared.channels.mail.processor import MailProcessor + try: - from kairon.shared.channels.mail.processor import MailProcessor mail_processor = MailProcessor(bot) interval = mail_processor.config.get("interval", 60) event_id = mail_processor.state.event_id diff --git a/kairon/shared/channels/mail/data_objects.py b/kairon/shared/channels/mail/data_objects.py index 93d49a6c6..ab387e0eb 100644 --- a/kairon/shared/channels/mail/data_objects.py +++ b/kairon/shared/channels/mail/data_objects.py @@ -1,10 +1,8 @@ import time from enum import Enum -from mongoengine import Document, StringField, ListField, FloatField, BooleanField, DictField, IntField -from kairon.exceptions import AppException +from mongoengine import Document, StringField, ListField, FloatField, DictField, IntField from kairon.shared.data.audit.data_objects import Auditlog -from kairon.shared.data.signals import auditlog, push_notification diff --git a/kairon/shared/channels/mail/processor.py b/kairon/shared/channels/mail/processor.py index cd09bc959..e17d33ded 100644 --- a/kairon/shared/channels/mail/processor.py +++ b/kairon/shared/channels/mail/processor.py @@ -119,6 +119,7 @@ async def send_mail(self, to: str, subject: str, body: str, log_id: str): logger.error(f"Error sending mail to {to}: {str(e)}") mail_log = MailResponseLog.objects.get(id=log_id) mail_log.status = MailStatus.FAILED.value + mail_log.responses.append(str(e)) mail_log.save() def process_mail(self, rasa_chat_response: dict, log_id: str): diff --git a/tests/unit_test/channels/mail_channel_test.py b/tests/unit_test/channels/mail_channel_test.py index 6cc3aa750..c4cd2f127 100644 --- a/tests/unit_test/channels/mail_channel_test.py +++ b/tests/unit_test/channels/mail_channel_test.py @@ -9,7 +9,7 @@ from uuid6 import uuid7 from kairon import Utility -from kairon.shared.channels.mail.data_objects import MailResponseLog, MailChannelStateData +from kairon.shared.channels.mail.data_objects import MailResponseLog, MailChannelStateData, MailStatus os.environ["system_file"] = "./tests/testing_data/system.yaml" Utility.load_environment() @@ -200,6 +200,44 @@ async def test_send_mail(self, mock_get_channel_config, mock_smtp): assert "Test Subject" in mock_smtp_instance.sendmail.call_args[0][2] assert "Test Body" in mock_smtp_instance.sendmail.call_args[0][2] + @patch("kairon.shared.channels.mail.processor.smtplib.SMTP") + @patch("kairon.shared.chat.processor.ChatDataProcessor.get_channel_config") + @pytest.mark.asyncio + async def test_send_mail_exception(self, mock_get_channel_config, mock_smtp): + mock_smtp_instance = MagicMock() + mock_smtp.return_value = mock_smtp_instance + + mail_response_log = MailResponseLog(bot=pytest.mail_test_bot, + sender_id="recipient@test.com", + user="mail_channel_test_user_acc", + subject="Test Subject", + body="Test Body", + ) + mail_response_log.save() + + mock_get_channel_config.return_value = { + 'config': { + 'email_account': "mail_channel_test_user_acc@testuser.com", + 'email_password': "password", + 'smtp_server': "smtp.testuser.com", + 'smtp_port': 587 + } + } + + bot_id = pytest.mail_test_bot + mp = MailProcessor(bot=bot_id) + mp.login_smtp() + + mock_smtp_instance.sendmail.side_effect = Exception("SMTP error") + + await mp.send_mail("recipient@test.com", "Test Subject", "Test Body", mail_response_log.id) + + log = MailResponseLog.objects.get(id=mail_response_log.id) + print(log.to_mongo()) + assert log.status == MailStatus.FAILED.value + assert log.responses == ['SMTP error'] + MailResponseLog.objects().delete() + @patch("kairon.shared.channels.mail.processor.ChatDataProcessor.get_channel_config") @@ -379,34 +417,49 @@ async def test_process_messages_exception(self, mock_exc): @patch('kairon.shared.channels.mail.processor.MailProcessor.login_smtp') @patch('kairon.shared.channels.mail.processor.MailProcessor.logout_smtp') def test_validate_smpt_connection(self, mp, mock_logout_smtp, mock_login_smtp): - # Mock the login and logout methods to avoid actual SMTP server interaction mp.return_value = None mock_login_smtp.return_value = None mock_logout_smtp.return_value = None - # Call the static method validate_smpt_connection result = MailProcessor.validate_smtp_connection('test_bot_id') - # Assert that the method returns True assert result - # Assert that login_smtp and logout_smtp were called once mock_login_smtp.assert_called_once() mock_logout_smtp.assert_called_once() @patch('kairon.shared.channels.mail.processor.MailProcessor.login_smtp') @patch('kairon.shared.channels.mail.processor.MailProcessor.logout_smtp') def test_validate_smpt_connection_failure(self, mock_logout_smtp, mock_login_smtp): - # Mock the login method to raise an exception mock_login_smtp.side_effect = Exception("SMTP login failed") - # Call the static method validate_smpt_connection result = MailProcessor.validate_smtp_connection('test_bot_id') - # Assert that the method returns False assert not result + @patch('kairon.shared.channels.mail.processor.MailProcessor.__init__') + @patch('kairon.shared.channels.mail.processor.MailProcessor.login_imap') + @patch('kairon.shared.channels.mail.processor.MailProcessor.logout_imap') + def test_validate_imap_connection(self, mp, mock_logout_imap, mock_login_imap): + mp.return_value = None + mock_login_imap.return_value = None + mock_logout_imap.return_value = None + + result = MailProcessor.validate_imap_connection('test_bot_id') + + assert result + mock_login_imap.assert_called_once() + mock_logout_imap.assert_called_once() + + @patch('kairon.shared.channels.mail.processor.MailProcessor.login_imap') + @patch('kairon.shared.channels.mail.processor.MailProcessor.logout_imap') + def test_validate_imap_connection_failure(self, mock_logout_imap, mock_login_imap): + mock_login_imap.side_effect = Exception("imap login failed") + + result = MailProcessor.validate_imap_connection('test_bot_id') + + assert not result def test_get_mail_channel_state_data_existing_state(self): bot_id = pytest.mail_test_bot diff --git a/tests/unit_test/channels/mail_scheduler_test.py b/tests/unit_test/channels/mail_scheduler_test.py index ad7fb123e..8df57f86d 100644 --- a/tests/unit_test/channels/mail_scheduler_test.py +++ b/tests/unit_test/channels/mail_scheduler_test.py @@ -43,6 +43,18 @@ def test_request_epoch_success(mock_execute_http_request, mock_get_event_server_ except AppException: pytest.fail("request_epoch() raised AppException unexpectedly!") +@patch('kairon.shared.channels.mail.processor.MailProcessor.validate_smtp_connection') +@patch('kairon.shared.channels.mail.processor.MailProcessor.validate_imap_connection') +@patch('kairon.shared.channels.mail.scheduler.Utility.get_event_server_url') +@patch('kairon.shared.channels.mail.scheduler.Utility.execute_http_request') +def test_request_epoch__response_not_success(mock_execute_http_request, mock_get_event_server_url, mock_imp, mock_smpt): + mock_get_event_server_url.return_value = "http://localhost" + mock_execute_http_request.return_value = {'success': False} + bot = "test_bot" + with pytest.raises(AppException): + MailScheduler.request_epoch(bot) + + @patch('kairon.shared.channels.mail.scheduler.Utility.get_event_server_url') @patch('kairon.shared.channels.mail.scheduler.Utility.execute_http_request') def test_request_epoch_failure(mock_execute_http_request, mock_get_event_server_url): @@ -53,17 +65,75 @@ def test_request_epoch_failure(mock_execute_http_request, mock_get_event_server_ MailScheduler.request_epoch("test_bot") -# @patch("kairon.shared.channels.mail.processor.MailProcessor.read_mails") -# @patch("kairon.shared.channels.mail.scheduler.MailChannelScheduleEvent.enqueue") -# @patch("kairon.shared.channels.mail.scheduler.datetime") -# def test_read_mailbox_and_schedule_events(mock_datetime, mock_enqueue, mock_read_mails): -# bot = "test_bot" -# fixed_now = datetime(2024, 12, 1, 20, 41, 55, 390288) -# mock_datetime.now.return_value = fixed_now -# mock_read_mails.return_value = ([ -# {"subject": "Test Subject", "mail_id": "test@example.com", "date": "2023-10-10", "body": "Test Body"} -# ], "mail_channel_test_user_acc", 1200) -# next_timestamp = MailScheduler.read_mailbox_and_schedule_events(bot) -# mock_read_mails.assert_called_once_with(bot) -# mock_enqueue.assert_called_once() -# assert next_timestamp == fixed_now + timedelta(seconds=1200) \ No newline at end of file + +@patch('kairon.events.utility.KScheduler.add_job') +@patch('kairon.events.utility.KScheduler.update_job') +@patch('kairon.events.utility.KScheduler.__init__', return_value=None) +@patch('kairon.shared.channels.mail.processor.MailProcessor') +@patch('pymongo.MongoClient', autospec=True) +def test_schedule_channel_mail_reading(mock_mongo, mock_mail_processor, mock_kscheduler, mock_update_job, mock_add_job): + from kairon.events.utility import EventUtility + + bot = "test_bot" + mock_mail_processor_instance = mock_mail_processor.return_value + mock_mail_processor_instance.config = {"interval": 1} + mock_mail_processor_instance.state.event_id = None + mock_mail_processor_instance.bot_settings.user = "test_user" + +# # Test case when event_id is None + EventUtility.schedule_channel_mail_reading(bot) + mock_add_job.assert_called_once() + mock_update_job.assert_not_called() + + mock_add_job.reset_mock() + mock_update_job.reset_mock() + mock_mail_processor_instance.state.event_id = "existing_event_id" + + # Test case when event_id exists + EventUtility.schedule_channel_mail_reading(bot) + mock_update_job.assert_called_once() + mock_add_job.assert_not_called() + +@patch('kairon.events.utility.KScheduler.add_job') +@patch('kairon.events.utility.KScheduler', autospec=True) +@patch('kairon.shared.channels.mail.processor.MailProcessor') +@patch('pymongo.MongoClient', autospec=True) +def test_schedule_channel_mail_reading_exception(mock_mongo_client, mock_mail_processor, mock_kscheduler, mock_add_job): + from kairon.events.utility import EventUtility + + bot = "test_bot" + mock_mail_processor.side_effect = Exception("Test Exception") + + with pytest.raises(AppException) as excinfo: + EventUtility.schedule_channel_mail_reading(bot) + assert str(excinfo.value) == f"Failed to schedule mail reading for bot {bot}. Error: Test Exception" + + +@patch('kairon.events.utility.EventUtility.schedule_channel_mail_reading') +@patch('kairon.shared.chat.data_objects.Channels.objects') +def test_reschedule_all_bots_channel_mail_reading(mock_channels_objects, mock_schedule_channel_mail_reading): + from kairon.events.utility import EventUtility + + mock_channels_objects.return_value.distinct.return_value = ['bot1', 'bot2'] + + EventUtility.reschedule_all_bots_channel_mail_reading() + + mock_channels_objects.return_value.distinct.assert_called_once_with("bot") + assert mock_schedule_channel_mail_reading.call_count == 2 + mock_schedule_channel_mail_reading.assert_any_call('bot1') + mock_schedule_channel_mail_reading.assert_any_call('bot2') + +@patch('kairon.events.utility.EventUtility.schedule_channel_mail_reading') +@patch('kairon.shared.chat.data_objects.Channels.objects') +def test_reschedule_all_bots_channel_mail_reading_exception(mock_channels_objects, mock_schedule_channel_mail_reading): + from kairon.events.utility import EventUtility + + mock_channels_objects.return_value.distinct.return_value = ['bot1', 'bot2'] + mock_schedule_channel_mail_reading.side_effect = Exception("Test Exception") + + with pytest.raises(AppException) as excinfo: + EventUtility.reschedule_all_bots_channel_mail_reading() + + assert str(excinfo.value) == "Failed to reschedule mail reading events. Error: Test Exception" + mock_channels_objects.return_value.distinct.assert_called_once_with("bot") + assert mock_schedule_channel_mail_reading.call_count == 1 \ No newline at end of file