# Path: responsebot/common/exceptions.py
# class MissingConfigError(ResponseBotError):
#     """
#     Exception to indicate a required configuration for Tweet Bot is not found from config file or CLI.
#     """
#     pass
# 
# class APIQuotaError(APIError):
#     """
#     Exception indicate some API quota breached.
#     """
#     pass
# 
# class AuthenticationError(ResponseBotError):
#     """
#     Error to indicate Tweet Bot failed to authenticate with the provided credentials.
#     """
#     pass
# 
# Path: responsebot/responsebot.py
# class ResponseBot(object):
#     """
#     The entry point of ResponseBot. :func:`~responsebot.responsebot.ResponseBot.__init__` inits & configures the bot.
#     :func:`~responsebot.responsebot.ResponseBot.start` tries to init the main sub-components (handler discoverer,
#     authenticator, tweet streamer, etc.) and handle errors if necessary
#     """
#     def __init__(self, *args, **kwargs):
#         """
#         Try to init & configure the bot from configurations read from ``.responsebot`` file, from CLI arguments or from
#         direct call in code
# 
#         :param kwargs: Options for :class:`~responsebot.utils.config_utils.ResponseBotConfig`
#         """
#         configure_logging()
#         try:
#             self.config = ResponseBotConfig(*args, **kwargs)
#         except MissingConfigError as e:
#             logging.error(str(e))
#             sys.exit()
# 
#         self.error_time_log = {}
#         self.error_sleep_log = {}
# 
#     def start(self):
#         """
#         Try to init the main sub-components (:func:`~responsebot.utils.handler_utils.discover_handler_classes`, \
#         :func:`~responsebot.utils.auth_utils.auth`, :class:`~responsebot.responsebot_stream.ResponseBotStream`, etc.)
#         """
#         logging.info('ResponseBot started')
# 
#         handler_classes = handler_utils.discover_handler_classes(self.config.get('handlers_package'))
#         if len(handler_classes) == 0:
#             logging.warning('No handler found. Did you forget to extend BaseTweethandler? Check --handlers-module')
# 
#         while True:
#             try:
#                 client = auth_utils.auth(self.config)
# 
#                 listener = ResponseBotListener(client=client, handler_classes=handler_classes)
# 
#                 stream = ResponseBotStream(client=client, listener=listener)
#                 stream.start()
#             except (APIQuotaError, AuthenticationError, TweepError) as e:
#                 self.handle_error(e)
#             else:
#                 break
# 
#     def handle_error(self, error):
#         """
#         Try to detect repetitive errors and sleep for a while to avoid being marked as spam
#         """
#         logging.exception("try to sleep if there are repeating errors.")
#         error_desc = str(error)
#         now = datetime.datetime.now()
#         if error_desc not in self.error_time_log:
#             self.error_time_log[error_desc] = now
#             return
# 
#         time_of_last_encounter = self.error_time_log[str(error)]
#         time_since_last_encounter = now - time_of_last_encounter
#         if time_since_last_encounter.total_seconds() > self.config.get('min_seconds_between_errors'):
#             self.error_time_log[error_desc] = now
#             return
# 
#         if error_desc not in self.error_sleep_log:
#             time.sleep(self.config.get('sleep_seconds_on_consecutive_errors'))
#             self.error_sleep_log[error_desc] = 1
#         else:
#             sys.exit()


from unittest.case import TestCase
from tweepy.error import TweepError
from responsebot.common.exceptions import MissingConfigError, APIQuotaError, AuthenticationError
    from mock import patch, MagicMock
    from unittest.mock import patch, MagicMock
from responsebot.responsebot import ResponseBot



class ResponseBotTestCase(TestCase):
    def test_call_necessary_utils(self):
        handler_classes = [MagicMock]
        client = MagicMock()
        listener = MagicMock()

        with patch('responsebot.utils.handler_utils.discover_handler_classes', return_value=handler_classes) \
                as mock_discover, \
                patch('responsebot.utils.auth_utils.auth', return_value=client) as mock_auth, \
                patch('responsebot.responsebot.ResponseBotListener', return_value=listener) as mock_listener, \
                patch('responsebot.responsebot.ResponseBotStream') as mock_stream:
            ResponseBot(
                handlers_package='some_package',
                auth=('ck', 'cs', 'tk', 'ts'),
            ).start()

            self.assertTrue(mock_auth.called)
            self.assertTrue(mock_discover.called)
            mock_listener.assert_called_once_with(client=client, handler_classes=handler_classes)
            mock_stream.assert_called_once_with(client=client, listener=listener)
            mock_stream().start.assert_called_once_with()

    @patch('logging.error')
    def test_log_missing_config_exception(self, mock_log):
        exception = MissingConfigError('message')

        with patch('responsebot.responsebot.ResponseBotConfig', side_effect=exception):

# Path: responsebot/common/exceptions.py
# class MissingConfigError(ResponseBotError):
#     """
#     Exception to indicate a required configuration for Tweet Bot is not found from config file or CLI.
#     """
#     pass
# 
# class APIQuotaError(APIError):
#     """
#     Exception indicate some API quota breached.
#     """
#     pass
# 
# class AuthenticationError(ResponseBotError):
#     """
#     Error to indicate Tweet Bot failed to authenticate with the provided credentials.
#     """
#     pass
# 
# Path: responsebot/responsebot.py
# class ResponseBot(object):
#     """
#     The entry point of ResponseBot. :func:`~responsebot.responsebot.ResponseBot.__init__` inits & configures the bot.
#     :func:`~responsebot.responsebot.ResponseBot.start` tries to init the main sub-components (handler discoverer,
#     authenticator, tweet streamer, etc.) and handle errors if necessary
#     """
#     def __init__(self, *args, **kwargs):
#         """
#         Try to init & configure the bot from configurations read from ``.responsebot`` file, from CLI arguments or from
#         direct call in code
# 
#         :param kwargs: Options for :class:`~responsebot.utils.config_utils.ResponseBotConfig`
#         """
#         configure_logging()
#         try:
#             self.config = ResponseBotConfig(*args, **kwargs)
#         except MissingConfigError as e:
#             logging.error(str(e))
#             sys.exit()
# 
#         self.error_time_log = {}
#         self.error_sleep_log = {}
# 
#     def start(self):
#         """
#         Try to init the main sub-components (:func:`~responsebot.utils.handler_utils.discover_handler_classes`, \
#         :func:`~responsebot.utils.auth_utils.auth`, :class:`~responsebot.responsebot_stream.ResponseBotStream`, etc.)
#         """
#         logging.info('ResponseBot started')
# 
#         handler_classes = handler_utils.discover_handler_classes(self.config.get('handlers_package'))
#         if len(handler_classes) == 0:
#             logging.warning('No handler found. Did you forget to extend BaseTweethandler? Check --handlers-module')
# 
#         while True:
#             try:
#                 client = auth_utils.auth(self.config)
# 
#                 listener = ResponseBotListener(client=client, handler_classes=handler_classes)
# 
#                 stream = ResponseBotStream(client=client, listener=listener)
#                 stream.start()
#             except (APIQuotaError, AuthenticationError, TweepError) as e:
#                 self.handle_error(e)
#             else:
#                 break
# 
#     def handle_error(self, error):
#         """
#         Try to detect repetitive errors and sleep for a while to avoid being marked as spam
#         """
#         logging.exception("try to sleep if there are repeating errors.")
#         error_desc = str(error)
#         now = datetime.datetime.now()
#         if error_desc not in self.error_time_log:
#             self.error_time_log[error_desc] = now
#             return
# 
#         time_of_last_encounter = self.error_time_log[str(error)]
#         time_since_last_encounter = now - time_of_last_encounter
#         if time_since_last_encounter.total_seconds() > self.config.get('min_seconds_between_errors'):
#             self.error_time_log[error_desc] = now
#             return
# 
#         if error_desc not in self.error_sleep_log:
#             time.sleep(self.config.get('sleep_seconds_on_consecutive_errors'))
#             self.error_sleep_log[error_desc] = 1
#         else:
#             sys.exit()


# Path: tests/unit_tests/test_responsebot.py
from unittest.case import TestCase
from tweepy.error import TweepError
from responsebot.common.exceptions import MissingConfigError, APIQuotaError, AuthenticationError
    from mock import patch, MagicMock
    from unittest.mock import patch, MagicMock
from responsebot.responsebot import ResponseBot




class ResponseBotTestCase(TestCase):
    def test_call_necessary_utils(self):
        handler_classes = [MagicMock]
        client = MagicMock()
        listener = MagicMock()

        with patch('responsebot.utils.handler_utils.discover_handler_classes', return_value=handler_classes) \
                as mock_discover, \
                patch('responsebot.utils.auth_utils.auth', return_value=client) as mock_auth, \
                patch('responsebot.responsebot.ResponseBotListener', return_value=listener) as mock_listener, \
                patch('responsebot.responsebot.ResponseBotStream') as mock_stream:
            ResponseBot(
                handlers_package='some_package',
                auth=('ck', 'cs', 'tk', 'ts'),
            ).start()

            self.assertTrue(mock_auth.called)
            self.assertTrue(mock_discover.called)
            mock_listener.assert_called_once_with(client=client, handler_classes=handler_classes)
            mock_stream.assert_called_once_with(client=client, listener=listener)
            mock_stream().start.assert_called_once_with()

    @patch('logging.error')
    def test_log_missing_config_exception(self, mock_log):
        exception = MissingConfigError('message')

        with patch('responsebot.responsebot.ResponseBotConfig', side_effect=exception):

            self.assertRaises(SystemExit, ResponseBot)