# 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
            self.assertRaises(SystemExit, ResponseBot)
            mock_log.assert_called_once_with('message')

    @patch('logging.warning')
    def test_log_no_handler_classes_warning(self, mock_log):
        with patch('responsebot.responsebot.ResponseBotConfig'),\
                patch('responsebot.utils.handler_utils.discover_handler_classes', return_value=[]),\
                patch('responsebot.utils.auth_utils.auth'),\
                patch('responsebot.responsebot.ResponseBotStream'):
            ResponseBot().start()
            mock_log.assert_called_once_with('No handler found. Did you forget to extend BaseTweethandler?'
                                             ' Check --handlers-module')

    @patch('logging.exception')
    def test_log_auth_error(self, mock_log):
        for exception in [APIQuotaError('message'), AuthenticationError('message')]:
            mock_log.reset_mock()
            with patch('responsebot.responsebot.ResponseBotConfig', return_value={
                'min_seconds_between_errors': 30, 'sleep_seconds_on_consecutive_errors': 300
                }),\
                    patch('responsebot.utils.handler_utils.discover_handler_classes', return_value=[MagicMock]),\
                    patch('responsebot.utils.auth_utils.auth', side_effect=exception),\
                    patch('responsebot.responsebot.time.sleep'):
                self.assertRaises(SystemExit, ResponseBot().start)
                mock_log.assert_called_with('try to sleep if there are repeating errors.')

    @patch('logging.exception')
    def test_log_stream_api_error(self, mock_log):
        exception = TweepError('message')
        with patch('responsebot.responsebot.ResponseBotConfig', return_value={

# 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

            self.assertRaises(SystemExit, ResponseBot)
            mock_log.assert_called_once_with('message')

    @patch('logging.warning')
    def test_log_no_handler_classes_warning(self, mock_log):
        with patch('responsebot.responsebot.ResponseBotConfig'),\
                patch('responsebot.utils.handler_utils.discover_handler_classes', return_value=[]),\
                patch('responsebot.utils.auth_utils.auth'),\
                patch('responsebot.responsebot.ResponseBotStream'):
            ResponseBot().start()
            mock_log.assert_called_once_with('No handler found. Did you forget to extend BaseTweethandler?'
                                             ' Check --handlers-module')

    @patch('logging.exception')
    def test_log_auth_error(self, mock_log):
        for exception in [APIQuotaError('message'), AuthenticationError('message')]:
            mock_log.reset_mock()
            with patch('responsebot.responsebot.ResponseBotConfig', return_value={
                'min_seconds_between_errors': 30, 'sleep_seconds_on_consecutive_errors': 300
                }),\
                    patch('responsebot.utils.handler_utils.discover_handler_classes', return_value=[MagicMock]),\
                    patch('responsebot.utils.auth_utils.auth', side_effect=exception),\
                    patch('responsebot.responsebot.time.sleep'):
                self.assertRaises(SystemExit, ResponseBot().start)
                mock_log.assert_called_with('try to sleep if there are repeating errors.')

    @patch('logging.exception')
    def test_log_stream_api_error(self, mock_log):
        exception = TweepError('message')
        with patch('responsebot.responsebot.ResponseBotConfig', return_value={

                'min_seconds_between_errors': 30, 'sleep_seconds_on_consecutive_errors': 300