Example #1
0
def _parse(json_data):
    """Try and parse the JSON returned from Telegram and return an empty
    dictionary if there is any error.

    Args:
      url:
        urllib.urlopen object

    Returns:
      A JSON parsed as Python dict with results.
    """
    decoded_s = json_data.decode('utf-8')
    try:
        data = json.loads(decoded_s)
    except ValueError:
        raise TelegramError('Invalid server response')

    if not data.get('ok') and data.get('description'):
        return data['description']

    return data['result']
Example #2
0
def show_backups(bot, update):
    try:
        bucket = get_env('S3_BUCKET_NAME')
        backup_path = get_env('S3_BACKUP_PATH')
        endpoint_url = get_env('ENDPOINT_URL')
    except NotConfigured as exception:
        raise TelegramError(str(exception))

    def _process(stdout):
        backups = []
        aws_ls = stdout.split('\n')
        for entry in aws_ls[:-1]:
            match = re.match('.+backup_(?P<date>[\dT-]{19}Z)_(?P<tag>.+)\.pg_dump$', entry)
            if match:
                data = match.groupdict()
                backups.append(f"{data['date']} - {data['tag']}")
        backups.reverse()
        message = '\n'.join(backups[:5])
        return message

    run_and_reply(update, f'aws --endpoint-url={endpoint_url} s3 ls s3://{bucket}{backup_path}', process_func=_process)
Example #3
0
    def _start_webhook(self, listen, port, url_path, cert, key,
                       bootstrap_retries, clean, webhook_url, allowed_updates):
        self.logger.debug('Updater thread started (webhook)')
        use_ssl = cert is not None and key is not None
        if not url_path.startswith('/'):
            url_path = '/{0}'.format(url_path)

        # Create Tornado app instance
        app = WebhookAppClass(url_path, self.bot, self.update_queue)

        # Form SSL Context
        # An SSLError is raised if the private key does not match with the certificate
        ssl_ctx = None
        if use_ssl:
            try:
                ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
                ssl_ctx.load_cert_chain(cert, key)
            except ssl.SSLError:
                raise TelegramError('SSL Certificate invalid')

        # Create and start server
        if asyncio:
            asyncio.set_event_loop(asyncio.new_event_loop())
        self.httpd = WebhookServer(port, app, ssl_ctx)

        if use_ssl:
            # DO NOT CHANGE: Only set webhook if SSL is handled by library
            if not webhook_url:
                webhook_url = self._gen_webhook_url(listen, port, url_path)

            self._bootstrap(max_retries=bootstrap_retries,
                            clean=clean,
                            webhook_url=webhook_url,
                            cert=open(cert, 'rb'),
                            allowed_updates=allowed_updates)
        elif clean:
            self.logger.warning("cleaning updates is not supported if "
                                "SSL-termination happens elsewhere; skipping")

        self.httpd.serve_forever()
Example #4
0
    def _start_webhook(self, host, port, cert, key, listen):
        self.logger.info('Updater thread started')
        url_base = "https://%s:%d" % (host, port)
        url_path = "/%s" % self.bot.token

        # Remove webhook
        self.bot.setWebhook(webhook_url=None)

        # Set webhook
        self.bot.setWebhook(webhook_url=url_base + url_path,
                            certificate=open(cert, 'rb'))

        # Start server
        self.httpd = WebhookServer((listen, port), WebhookHandler,
                                   self.update_queue, url_path)

        # Check SSL-Certificate with openssl, if possible
        try:
            DEVNULL = open(os.devnull, 'wb')
            exit_code = subprocess.call(
                ["openssl", "x509", "-text", "-noout", "-in", cert],
                stdout=DEVNULL,
                stderr=subprocess.STDOUT)
        except OSError:
            exit_code = 0

        if exit_code is 0:
            try:
                self.httpd.socket = ssl.wrap_socket(self.httpd.socket,
                                                    certfile=cert,
                                                    keyfile=key,
                                                    server_side=True)
                self.httpd.serve_forever(poll_interval=1)
            except ssl.SSLError as error:
                self.logger.error(str(error))
            finally:
                self.logger.info('Updater thread stopped')
        else:
            raise TelegramError('SSL Certificate invalid')
Example #5
0
    def start(self):
        """
        Thread target of thread 'dispatcher'. Runs in background and processes
        the update queue.
        """

        if self.running:
            self.logger.warning('already running')
            return

        if self.__exception_event.is_set():
            msg = 'reusing dispatcher after exception event is forbidden'
            self.logger.error(msg)
            raise TelegramError(msg)

        self._init_async_threads(uuid4(), self.workers)
        self.running = True
        self.logger.debug('Dispatcher started')

        while 1:
            try:
                # Pop update from update queue.
                update = self.update_queue.get(True, 1)
            except Empty:
                if self.__stop_event.is_set():
                    self.logger.debug('orderly stopping')
                    break
                elif self.__exception_event.is_set():
                    self.logger.critical(
                        'stopping due to exception in another thread')
                    break
                continue

            self.logger.debug('Processing Update: %s' % update)
            self.process_update(update)

        self.running = False
        self.logger.debug('Dispatcher thread stopped')
    def __init__(self, data):
        self.data = data
        self.boundary = choose_boundary()

        for t in FILE_TYPES:
            if t in data:
                self.input_name = t
                self.input_file = data.pop(t)
                break
        else:
            raise TelegramError('Unknown inputfile type')

        if hasattr(self.input_file, 'read'):
            self.filename = None
            self.input_file_content = self.input_file.read()
            if 'filename' in data:
                self.filename = self.data.pop('filename')
            elif (hasattr(self.input_file, 'name') and
                  not isinstance(self.input_file.name, int) and  # py3
                  self.input_file.name != '<fdopen>'):  # py2
                # on py2.7, pylint fails to understand this properly
                # pylint: disable=E1101
                self.filename = os.path.basename(self.input_file.name)

            try:
                self.mimetype = self.is_image(self.input_file_content)
                if not self.filename or '.' not in self.filename:
                    self.filename = self.mimetype.replace('/', '.')
            except TelegramError:
                if self.filename:
                    self.mimetype = mimetypes.guess_type(
                        self.filename)[0] or DEFAULT_MIME_TYPE
                else:
                    self.mimetype = DEFAULT_MIME_TYPE

        if sys.version_info < (3,):
            if isinstance(self.filename, unicode):  # flake8: noqa  pylint: disable=E0602
                self.filename = self.filename.encode('utf-8', 'replace')
    def test_error_handler_that_raises_errors(self, dp):
        """
        Make sure that errors raised in error handlers don't break the main loop of the dispatcher
        """
        handler_raise_error = MessageHandler(Filters.all, self.callback_raise_error)
        handler_increase_count = MessageHandler(Filters.all, self.callback_increase_count)
        error = TelegramError('Unauthorized.')

        dp.add_error_handler(self.error_handler_raise_error)

        # From errors caused by handlers
        dp.add_handler(handler_raise_error)
        dp.update_queue.put(self.message_update)
        sleep(.1)

        # From errors in the update_queue
        dp.remove_handler(handler_raise_error)
        dp.add_handler(handler_increase_count)
        dp.update_queue.put(error)
        dp.update_queue.put(self.message_update)
        sleep(.1)

        assert self.count == 1
Example #8
0
    def _parse(json_data):
        """Try and parse the JSON returned from Telegram.

        Returns:
            dict: A JSON parsed as Python dict with results - on error this dict will be empty.

        """
        decoded_s = json_data.decode('utf-8')
        try:
            data = json.loads(decoded_s)
        except ValueError:
            raise TelegramError('Invalid server response')

        if not data.get('ok'):
            description = data.get('description')
            parameters = data.get('parameters')
            if parameters:
                migrate_to_chat_id = parameters.get('migrate_to_chat_id')
                if migrate_to_chat_id:
                    raise ChatMigrated(migrate_to_chat_id)
            if description:
                return description

        return data['result']
Example #9
0
    def test_flow_stop_in_error_handler(self, dp, bot):
        passed = []
        err = TelegramError('Telegram error')

        def start1(b, u):
            passed.append('start1')
            raise err

        def start2(b, u):
            passed.append('start2')

        def start3(b, u):
            passed.append('start3')

        def error(b, u, e):
            passed.append('error')
            passed.append(e)
            raise DispatcherHandlerStop

        update = Update(1,
                        message=Message(1,
                                        None,
                                        None,
                                        None,
                                        text='/start',
                                        bot=bot))

        # If a TelegramException was caught, an error handler should be called and no further
        # handlers from the same group should be called.
        dp.add_handler(CommandHandler('start', start1), 1)
        dp.add_handler(CommandHandler('start', start2), 1)
        dp.add_handler(CommandHandler('start', start3), 2)
        dp.add_error_handler(error)
        dp.process_update(update)
        assert passed == ['start1', 'error', err]
        assert passed[2] is err
Example #10
0
class TestUpdater:
    message_count = 0
    received = None
    attempts = 0
    err_handler_called = Event()
    cb_handler_called = Event()
    offset = 0
    test_flag = False

    def test_slot_behaviour(self, updater, mro_slots, recwarn):
        for at in updater.__slots__:
            at = f"_Updater{at}" if at.startswith(
                '__') and not at.endswith('__') else at
            assert getattr(updater, at,
                           'err') != 'err', f"got extra slot '{at}'"
        assert not updater.__dict__, f"got missing slot(s): {updater.__dict__}"
        assert len(mro_slots(updater)) == len(set(
            mro_slots(updater))), "duplicate slot"
        updater.custom, updater.running = 'should give warning', updater.running
        assert len(recwarn) == 1 and 'custom' in str(
            recwarn[0].message), recwarn.list

        class CustomUpdater(Updater):
            pass  # Tests that setting custom attributes of Updater subclass doesn't raise warning

        a = CustomUpdater(updater.bot.token)
        a.my_custom = 'no error!'
        assert len(recwarn) == 1

        updater.__setattr__('__test', 'mangled success')
        assert getattr(updater, '_Updater__test',
                       'e') == 'mangled success', "mangling failed"

    @pytest.fixture(autouse=True)
    def reset(self):
        self.message_count = 0
        self.received = None
        self.attempts = 0
        self.err_handler_called.clear()
        self.cb_handler_called.clear()
        self.test_flag = False

    def error_handler(self, bot, update, error):
        self.received = error.message
        self.err_handler_called.set()

    def callback(self, bot, update):
        self.received = update.message.text
        self.cb_handler_called.set()

    def test_warn_arbitrary_callback_data(self, bot, recwarn):
        Updater(bot=bot, arbitrary_callback_data=True)
        assert len(recwarn) == 1
        assert 'Passing arbitrary_callback_data to an Updater' in str(
            recwarn[0].message)

    @pytest.mark.parametrize(
        ('error', ),
        argvalues=[(TelegramError('Test Error 2'), ),
                   (Unauthorized('Test Unauthorized'), )],
        ids=('TelegramError', 'Unauthorized'),
    )
    def test_get_updates_normal_err(self, monkeypatch, updater, error):
        def test(*args, **kwargs):
            raise error

        monkeypatch.setattr(updater.bot, 'get_updates', test)
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        updater.dispatcher.add_error_handler(self.error_handler)
        updater.start_polling(0.01)

        # Make sure that the error handler was called
        self.err_handler_called.wait()
        assert self.received == error.message

        # Make sure that Updater polling thread keeps running
        self.err_handler_called.clear()
        self.err_handler_called.wait()

    @pytest.mark.filterwarnings(
        'ignore:.*:pytest.PytestUnhandledThreadExceptionWarning')
    def test_get_updates_bailout_err(self, monkeypatch, updater, caplog):
        error = InvalidToken()

        def test(*args, **kwargs):
            raise error

        with caplog.at_level(logging.DEBUG):
            monkeypatch.setattr(updater.bot, 'get_updates', test)
            monkeypatch.setattr(updater.bot, 'set_webhook',
                                lambda *args, **kwargs: True)
            updater.dispatcher.add_error_handler(self.error_handler)
            updater.start_polling(0.01)
            assert self.err_handler_called.wait(1) is not True

        sleep(1)
        # NOTE: This test might hit a race condition and fail (though the 1 seconds delay above
        #       should work around it).
        # NOTE: Checking Updater.running is problematic because it is not set to False when there's
        #       an unhandled exception.
        # TODO: We should have a way to poll Updater status and decide if it's running or not.
        import pprint

        pprint.pprint([rec.getMessage() for rec in caplog.get_records('call')])
        assert any(f'unhandled exception in Bot:{updater.bot.id}:updater' in
                   rec.getMessage() for rec in caplog.get_records('call'))

    @pytest.mark.parametrize(('error', ),
                             argvalues=[(RetryAfter(0.01), ), (TimedOut(), )],
                             ids=('RetryAfter', 'TimedOut'))
    def test_get_updates_retries(self, monkeypatch, updater, error):
        event = Event()

        def test(*args, **kwargs):
            event.set()
            raise error

        monkeypatch.setattr(updater.bot, 'get_updates', test)
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        updater.dispatcher.add_error_handler(self.error_handler)
        updater.start_polling(0.01)

        # Make sure that get_updates was called, but not the error handler
        event.wait()
        assert self.err_handler_called.wait(0.5) is not True
        assert self.received != error.message

        # Make sure that Updater polling thread keeps running
        event.clear()
        event.wait()
        assert self.err_handler_called.wait(0.5) is not True

    @pytest.mark.parametrize('ext_bot', [True, False])
    def test_webhook(self, monkeypatch, updater, ext_bot):
        # Testing with both ExtBot and Bot to make sure any logic in WebhookHandler
        # that depends on this distinction works
        if ext_bot and not isinstance(updater.bot, ExtBot):
            updater.bot = ExtBot(updater.bot.token)
        if not ext_bot and not type(updater.bot) is Bot:
            updater.bot = Bot(updater.bot.token)

        q = Queue()
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                            lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(ip, port, url_path='TOKEN')
        sleep(0.2)
        try:
            # Now, we send an update to the server via urlopen
            update = Update(
                1,
                message=Message(1,
                                None,
                                Chat(1, ''),
                                from_user=User(1, '', False),
                                text='Webhook'),
            )
            self._send_webhook_msg(ip, port, update.to_json(), 'TOKEN')
            sleep(0.2)
            assert q.get(False) == update

            # Returns 404 if path is incorrect
            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip, port, None, 'webookhandler.py')
            assert excinfo.value.code == 404

            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip,
                                       port,
                                       None,
                                       'webookhandler.py',
                                       get_method=lambda: 'HEAD')
            assert excinfo.value.code == 404

            # Test multiple shutdown() calls
            updater.httpd.shutdown()
        finally:
            updater.httpd.shutdown()
            sleep(0.2)
            assert not updater.httpd.is_running
            updater.stop()

    @pytest.mark.parametrize('invalid_data', [True, False])
    def test_webhook_arbitrary_callback_data(self, monkeypatch, updater,
                                             invalid_data):
        """Here we only test one simple setup. telegram.ext.ExtBot.insert_callback_data is tested
        extensively in test_bot.py in conjunction with get_updates."""
        updater.bot.arbitrary_callback_data = True
        try:
            q = Queue()
            monkeypatch.setattr(updater.bot, 'set_webhook',
                                lambda *args, **kwargs: True)
            monkeypatch.setattr(updater.bot, 'delete_webhook',
                                lambda *args, **kwargs: True)
            monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                                lambda _, u: q.put(u))

            ip = '127.0.0.1'
            port = randrange(1024, 49152)  # Select random port
            updater.start_webhook(ip, port, url_path='TOKEN')
            sleep(0.2)
            try:
                # Now, we send an update to the server via urlopen
                reply_markup = InlineKeyboardMarkup.from_button(
                    InlineKeyboardButton(text='text',
                                         callback_data='callback_data'))
                if not invalid_data:
                    reply_markup = updater.bot.callback_data_cache.process_keyboard(
                        reply_markup)

                message = Message(
                    1,
                    None,
                    None,
                    reply_markup=reply_markup,
                )
                update = Update(1, message=message)
                self._send_webhook_msg(ip, port, update.to_json(), 'TOKEN')
                sleep(0.2)
                received_update = q.get(False)
                assert received_update == update

                button = received_update.message.reply_markup.inline_keyboard[
                    0][0]
                if invalid_data:
                    assert isinstance(button.callback_data,
                                      InvalidCallbackData)
                else:
                    assert button.callback_data == 'callback_data'

                # Test multiple shutdown() calls
                updater.httpd.shutdown()
            finally:
                updater.httpd.shutdown()
                sleep(0.2)
                assert not updater.httpd.is_running
                updater.stop()
        finally:
            updater.bot.arbitrary_callback_data = False
            updater.bot.callback_data_cache.clear_callback_data()
            updater.bot.callback_data_cache.clear_callback_queries()

    def test_start_webhook_no_warning_or_error_logs(self, caplog, updater,
                                                    monkeypatch):
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        # prevent api calls from @info decorator when updater.bot.id is used in thread names
        monkeypatch.setattr(updater.bot, '_bot',
                            User(id=123, first_name='bot', is_bot=True))
        monkeypatch.setattr(updater.bot, '_commands', [])

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        with caplog.at_level(logging.WARNING):
            updater.start_webhook(ip, port)
            updater.stop()
        assert not caplog.records

    def test_webhook_ssl(self, monkeypatch, updater):
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        tg_err = False
        try:
            updater._start_webhook(
                ip,
                port,
                url_path='TOKEN',
                cert='./tests/test_updater.py',
                key='./tests/test_updater.py',
                bootstrap_retries=0,
                drop_pending_updates=False,
                webhook_url=None,
                allowed_updates=None,
            )
        except TelegramError:
            tg_err = True
        assert tg_err

    def test_webhook_no_ssl(self, monkeypatch, updater):
        q = Queue()
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                            lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(ip, port, webhook_url=None)
        sleep(0.2)

        # Now, we send an update to the server via urlopen
        update = Update(
            1,
            message=Message(1,
                            None,
                            Chat(1, ''),
                            from_user=User(1, '', False),
                            text='Webhook 2'),
        )
        self._send_webhook_msg(ip, port, update.to_json())
        sleep(0.2)
        assert q.get(False) == update
        updater.stop()

    def test_webhook_ssl_just_for_telegram(self, monkeypatch, updater):
        q = Queue()

        def set_webhook(**kwargs):
            self.test_flag.append(bool(kwargs.get('certificate')))
            return True

        orig_wh_server_init = WebhookServer.__init__

        def webhook_server_init(*args):
            self.test_flag = [args[-1] is None]
            orig_wh_server_init(*args)

        monkeypatch.setattr(updater.bot, 'set_webhook', set_webhook)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                            lambda _, u: q.put(u))
        monkeypatch.setattr(
            'telegram.ext.utils.webhookhandler.WebhookServer.__init__',
            webhook_server_init)

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(ip,
                              port,
                              webhook_url=None,
                              cert='./tests/test_updater.py')
        sleep(0.2)

        # Now, we send an update to the server via urlopen
        update = Update(
            1,
            message=Message(1,
                            None,
                            Chat(1, ''),
                            from_user=User(1, '', False),
                            text='Webhook 2'),
        )
        self._send_webhook_msg(ip, port, update.to_json())
        sleep(0.2)
        assert q.get(False) == update
        updater.stop()
        assert self.test_flag == [True, True]

    @pytest.mark.parametrize('pass_max_connections', [True, False])
    def test_webhook_max_connections(self, monkeypatch, updater,
                                     pass_max_connections):
        q = Queue()
        max_connections = 42

        def set_webhook(**kwargs):
            print(kwargs)
            self.test_flag = kwargs.get('max_connections') == (
                max_connections if pass_max_connections else 40)
            return True

        monkeypatch.setattr(updater.bot, 'set_webhook', set_webhook)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                            lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        if pass_max_connections:
            updater.start_webhook(ip,
                                  port,
                                  webhook_url=None,
                                  max_connections=max_connections)
        else:
            updater.start_webhook(ip, port, webhook_url=None)

        sleep(0.2)

        # Now, we send an update to the server via urlopen
        update = Update(
            1,
            message=Message(1,
                            None,
                            Chat(1, ''),
                            from_user=User(1, '', False),
                            text='Webhook 2'),
        )
        self._send_webhook_msg(ip, port, update.to_json())
        sleep(0.2)
        assert q.get(False) == update
        updater.stop()
        assert self.test_flag is True

    @pytest.mark.parametrize(('error', ),
                             argvalues=[(TelegramError(''), )],
                             ids=('TelegramError', ))
    def test_bootstrap_retries_success(self, monkeypatch, updater, error):
        retries = 2

        def attempt(*args, **kwargs):
            if self.attempts < retries:
                self.attempts += 1
                raise error

        monkeypatch.setattr(updater.bot, 'set_webhook', attempt)

        updater.running = True
        updater._bootstrap(retries, False, 'path', None, bootstrap_interval=0)
        assert self.attempts == retries

    @pytest.mark.parametrize(
        ('error', 'attempts'),
        argvalues=[(TelegramError(''), 2), (Unauthorized(''), 1),
                   (InvalidToken(), 1)],
        ids=('TelegramError', 'Unauthorized', 'InvalidToken'),
    )
    def test_bootstrap_retries_error(self, monkeypatch, updater, error,
                                     attempts):
        retries = 1

        def attempt(*args, **kwargs):
            self.attempts += 1
            raise error

        monkeypatch.setattr(updater.bot, 'set_webhook', attempt)

        updater.running = True
        with pytest.raises(type(error)):
            updater._bootstrap(retries,
                               False,
                               'path',
                               None,
                               bootstrap_interval=0)
        assert self.attempts == attempts

    @pytest.mark.parametrize('drop_pending_updates', (True, False))
    def test_bootstrap_clean_updates(self, monkeypatch, updater,
                                     drop_pending_updates):
        # As dropping pending updates is done by passing `drop_pending_updates` to
        # set_webhook, we just check that we pass the correct value
        self.test_flag = False

        def delete_webhook(**kwargs):
            self.test_flag = kwargs.get(
                'drop_pending_updates') == drop_pending_updates

        monkeypatch.setattr(updater.bot, 'delete_webhook', delete_webhook)

        updater.running = True
        updater._bootstrap(
            1,
            drop_pending_updates=drop_pending_updates,
            webhook_url=None,
            allowed_updates=None,
            bootstrap_interval=0,
        )
        assert self.test_flag is True

    def test_deprecation_warnings_start_webhook(self, recwarn, updater,
                                                monkeypatch):
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        # prevent api calls from @info decorator when updater.bot.id is used in thread names
        monkeypatch.setattr(updater.bot, '_bot',
                            User(id=123, first_name='bot', is_bot=True))
        monkeypatch.setattr(updater.bot, '_commands', [])

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(ip, port, clean=True, force_event_loop=False)
        updater.stop()

        for warning in recwarn:
            print(warning)

        try:  # This is for flaky tests (there's an unclosed socket sometimes)
            recwarn.pop(
                ResourceWarning
            )  # internally iterates through recwarn.list and deletes it
        except AssertionError:
            pass

        assert len(recwarn) == 3
        assert str(recwarn[0].message).startswith('Old Handler API')
        assert str(recwarn[1].message).startswith('The argument `clean` of')
        assert str(recwarn[2].message).startswith(
            'The argument `force_event_loop` of')

    def test_clean_deprecation_warning_polling(self, recwarn, updater,
                                               monkeypatch):
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        # prevent api calls from @info decorator when updater.bot.id is used in thread names
        monkeypatch.setattr(updater.bot, '_bot',
                            User(id=123, first_name='bot', is_bot=True))
        monkeypatch.setattr(updater.bot, '_commands', [])

        updater.start_polling(clean=True)
        updater.stop()
        for msg in recwarn:
            print(msg)

        try:  # This is for flaky tests (there's an unclosed socket sometimes)
            recwarn.pop(
                ResourceWarning
            )  # internally iterates through recwarn.list and deletes it
        except AssertionError:
            pass

        assert len(recwarn) == 2
        assert str(recwarn[0].message).startswith('Old Handler API')
        assert str(recwarn[1].message).startswith('The argument `clean` of')

    def test_clean_drop_pending_mutually_exclusive(self, updater):
        with pytest.raises(
                TypeError,
                match='`clean` and `drop_pending_updates` are mutually'):
            updater.start_polling(clean=True, drop_pending_updates=False)

        with pytest.raises(
                TypeError,
                match='`clean` and `drop_pending_updates` are mutually'):
            updater.start_webhook(clean=True, drop_pending_updates=False)

    @flaky(3, 1)
    def test_webhook_invalid_posts(self, updater):
        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # select random port for travis
        thr = Thread(target=updater._start_webhook,
                     args=(ip, port, '', None, None, 0, False, None, None))
        thr.start()

        sleep(0.2)

        try:
            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip,
                                       port,
                                       '<root><bla>data</bla></root>',
                                       content_type='application/xml')
            assert excinfo.value.code == 403

            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip,
                                       port,
                                       'dummy-payload',
                                       content_len=-2)
            assert excinfo.value.code == 500

            # TODO: prevent urllib or the underlying from adding content-length
            # with pytest.raises(HTTPError) as excinfo:
            #     self._send_webhook_msg(ip, port, 'dummy-payload', content_len=None)
            # assert excinfo.value.code == 411

            with pytest.raises(HTTPError):
                self._send_webhook_msg(ip,
                                       port,
                                       'dummy-payload',
                                       content_len='not-a-number')
            assert excinfo.value.code == 500

        finally:
            updater.httpd.shutdown()
            thr.join()

    def _send_webhook_msg(
        self,
        ip,
        port,
        payload_str,
        url_path='',
        content_len=-1,
        content_type='application/json',
        get_method=None,
    ):
        headers = {
            'content-type': content_type,
        }

        if not payload_str:
            content_len = None
            payload = None
        else:
            payload = bytes(payload_str, encoding='utf-8')

        if content_len == -1:
            content_len = len(payload)

        if content_len is not None:
            headers['content-length'] = str(content_len)

        url = f'http://{ip}:{port}/{url_path}'

        req = Request(url, data=payload, headers=headers)

        if get_method is not None:
            req.get_method = get_method

        return urlopen(req)

    def signal_sender(self, updater):
        sleep(0.2)
        while not updater.running:
            sleep(0.2)

        os.kill(os.getpid(), signal.SIGTERM)

    @signalskip
    def test_idle(self, updater, caplog):
        updater.start_polling(0.01)
        Thread(target=partial(self.signal_sender, updater=updater)).start()

        with caplog.at_level(logging.INFO):
            updater.idle()

        # There is a chance of a conflict when getting updates since there can be many tests
        # (bots) running simultaneously while testing in github actions.
        records = caplog.records.copy(
        )  # To avoid iterating and removing at same time
        for idx, log in enumerate(records):
            print(log)
            msg = log.getMessage()
            if msg.startswith('Error while getting Updates: Conflict'):
                caplog.records.pop(idx)  # For stability

            if msg.startswith('No error handlers are registered'):
                caplog.records.pop(idx)

        assert len(caplog.records) == 2, caplog.records

        rec = caplog.records[-2]
        assert rec.getMessage().startswith(f'Received signal {signal.SIGTERM}')
        assert rec.levelname == 'INFO'

        rec = caplog.records[-1]
        assert rec.getMessage().startswith('Scheduler has been shut down')
        assert rec.levelname == 'INFO'

        # If we get this far, idle() ran through
        sleep(0.5)
        assert updater.running is False

    @signalskip
    def test_user_signal(self, updater):
        temp_var = {'a': 0}

        def user_signal_inc(signum, frame):
            temp_var['a'] = 1

        updater.user_sig_handler = user_signal_inc
        updater.start_polling(0.01)
        Thread(target=partial(self.signal_sender, updater=updater)).start()
        updater.idle()
        # If we get this far, idle() ran through
        sleep(0.5)
        assert updater.running is False
        assert temp_var['a'] != 0

    def test_create_bot(self):
        updater = Updater('123:abcd')
        assert updater.bot is not None

    def test_mutual_exclude_token_bot(self):
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(token='123:abcd', bot=bot)

    def test_no_token_or_bot_or_dispatcher(self):
        with pytest.raises(ValueError):
            Updater()

    def test_mutual_exclude_bot_private_key(self):
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(bot=bot, private_key=b'key')

    def test_mutual_exclude_bot_dispatcher(self, bot):
        dispatcher = Dispatcher(bot, None)
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(bot=bot, dispatcher=dispatcher)

    def test_mutual_exclude_persistence_dispatcher(self, bot):
        dispatcher = Dispatcher(bot, None)
        persistence = DictPersistence()
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, persistence=persistence)

    def test_mutual_exclude_workers_dispatcher(self, bot):
        dispatcher = Dispatcher(bot, None)
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, workers=8)

    def test_mutual_exclude_use_context_dispatcher(self, bot):
        dispatcher = Dispatcher(bot, None)
        use_context = not dispatcher.use_context
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, use_context=use_context)

    def test_mutual_exclude_custom_context_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, context_types=True)

    def test_defaults_warning(self, bot):
        with pytest.warns(TelegramDeprecationWarning,
                          match='no effect when a Bot is passed'):
            Updater(bot=bot, defaults=Defaults())
Example #11
0
 def _valid_token(token):
     """a very basic validation on token"""
     left, sep, _right = token.partition(':')
     if (not sep) or (not left.isdigit()) or (len(left) < 3):
         raise TelegramError('Invalid token')
     return token
Example #12
0
 def errorRaisingHandlerTest(self, bot, update):
     raise TelegramError(update)
Example #13
0
 def creator(self):
     _id = helper.safe_list_get(self._data, "creator", None)
     if not _id or not isinstance(_id, int):
         raise TelegramError("Некорректный id желающего делать арт")
     return User.get(_id)
Example #14
0
 def increase_step(self, increase_step: int):
     raise TelegramError(
         "Increase step is being changed only via Firebase store")
Example #15
0
def sendToAll(bot, message, list_of_chats, user_chat_id):
    timeout = 10  #Timeout in seconds, though this might be a good idea, don't think this bot will be hitting this any time soon
    # This is the bulk of the work in this bot.

    if Filters.forwarded(message):
        message_id = message.message_id
        from_chat = message.forward_from_chat.id
        for chat in list_of_chats:
            try:
                bot.forward_message(chat_id=chat,
                                    from_chat_id=from_chat,
                                    message_id=message_id,
                                    timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send message to admin in sendToAll: %s" % te)

        try:
            newMessage = bot.forward_message(chat_id=user_chat_id,
                                             from_chat_id=from_chat,
                                             message_id=message_id,
                                             timeout=timeout)
        # If the user has not responded. Message all of the admins.
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:  #error checking in error checking because bot.sendMessage doesn't work for people who haven't messaged the bot.
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to forward message to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.text(message):
        for chat in list_of_chats:
            try:
                bot.send_message(chat_id=chat,
                                 text=message.text,
                                 timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send message to admin in sendToAll: %s" % te)
        try:
            newMessage = bot.send_message(chat_id=user_chat_id,
                                          text=message.text,
                                          timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send text message to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.audio(message):
        audio = message.audio.file_id
        for chat in list_of_chats:
            try:
                bot.send_audio(chat_id=chat, audio=audio, timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send message to admin in sendToAll: %s" % te)
        try:
            newMessage = bot.send_audio(chat_id=user_chat_id,
                                        audio=audio,
                                        timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send audio message to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.document(message):
        document = message.document.file_id
        for chat in list_of_chats:
            try:
                bot.send_document(chat_id=chat,
                                  document=document,
                                  timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send message to admin in sendToAll: %s" % te)
        try:
            newMessage = bot.send_document(chat_id=user_chat_id,
                                           document=document,
                                           timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send document to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.photo(message):
        photo = message.photo[0].file_id
        caption = ""
        if message.caption:
            caption = message.caption
        for chat in list_of_chats:
            try:
                bot.send_photo(chat_id=chat,
                               photo=photo,
                               caption=caption,
                               timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send message to admin in sendToAll: %s" % te)
        try:
            newMessage = bot.send_photo(chat_id=user_chat_id,
                                        photo=photo,
                                        caption=caption,
                                        timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send photo to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.sticker(message):
        sticker = message.sticker.file_id
        for chat in list_of_chats:
            try:
                bot.send_sticker(chat_id=chat,
                                 sticker=sticker,
                                 timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send messages to admin in SendToAll: %s" % te)
        try:
            newMessage = bot.send_sticker(chat_id=user_chat_id,
                                          sticker=sticker,
                                          timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send sticker to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.voice(message):
        voice = message.voice.file_id
        for chat in list_of_chats:
            try:
                bot.send_voice(chat_id=chat, voice=voice, timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send message to admin in sendToAll: %s " % te)
        try:
            newMessage = bot.send_voice(chat_id=user_chat_id,
                                        voice=voice,
                                        timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send voice message to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.video(message):
        video = message.video.file_id
        for chat in list_of_chats:
            try:
                bot.send_video(chat_id=chat, video=video, timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send message to admin in sendToAll: %s" % te)
        try:
            newMessage = bot.send_video(chat_id=user_chat_id,
                                        video=video,
                                        timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send message to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.contact(message):
        phone_number = message.contact.phone_number
        first_name = message.contact.first_name
        last_name = message.contact.last_name
        for chat in list_of_chats:
            try:
                bot.send_contact(chat_id=chat,
                                 phone_number=phone_number,
                                 first_name=first_name,
                                 last_name=last_name,
                                 timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unbable to send message to admin in sendToAll: %s" % te)
        try:
            newMessage = bot.send_contact(chat_id=user_chat_id,
                                          phone_number=phone_number,
                                          first_name=first_name,
                                          last_name=last_name,
                                          timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send contact to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    elif Filters.location(message):
        lat = message.location.latitude
        lon = message.location.longitude
        for chat in list_of_chats:
            try:
                bot.send_location(chat_id=chat,
                                  longitude=lon,
                                  latitude=lat,
                                  timeout=timeout)
            except TelegramError as te:
                logger.debug(
                    "Unable to send message to admin in sendToAll: %s" % te)
        try:
            newMessage = bot.send_location(chat_id=user_chat_id,
                                           longitude=lon,
                                           latitude=lat,
                                           timeout=timeout)
        except TelegramError as te:
            logger.debug("Unable to send message to user in sendToAll: %s" %
                         te)
            for chat in list_of_chats:
                try:
                    bot.sendMessage(
                        chat_id=chat,
                        text=
                        "Unable to send location to user, if this persists, resolve this thread, they may have stopped talking to the bot."
                    )
                except TelegramError:
                    pass

    else:
        logger.warning("Message %s not handled in SendToAll")
        raise TelegramError("No handler for forwarding.")

    MDB.active.update({'_id': user_chat_id},
                      {'$push': {
                          'log': newMessage.message_id
                      }})
Example #16
0
 def test(*args, **kwargs):
     raise TelegramError('Test Error 2')
Example #17
0
 def ticket_description(self, ticket_description: str):
     raise TelegramError("Direct setter for ticket_description is denied")
Example #18
0
        def decorator(self, *args, **kwargs):
            if not self.__auth:
                raise TelegramError({'message': "API must be authenticated."})

            return func(self, *args, **kwargs)
class TestErrors:
    def test_telegram_error(self):
        with pytest.raises(TelegramError, match="^test message$"):
            raise TelegramError("test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("Error: test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("[Error]: test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("Bad Request: test message")

    def test_unauthorized(self):
        with pytest.raises(Unauthorized, match="test message"):
            raise Unauthorized("test message")
        with pytest.raises(Unauthorized, match="^Test message$"):
            raise Unauthorized("Error: test message")
        with pytest.raises(Unauthorized, match="^Test message$"):
            raise Unauthorized("[Error]: test message")
        with pytest.raises(Unauthorized, match="^Test message$"):
            raise Unauthorized("Bad Request: test message")

    def test_invalid_token(self):
        with pytest.raises(InvalidToken, match="Invalid token"):
            raise InvalidToken

    def test_network_error(self):
        with pytest.raises(NetworkError, match="test message"):
            raise NetworkError("test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("Error: test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("[Error]: test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("Bad Request: test message")

    def test_bad_request(self):
        with pytest.raises(BadRequest, match="test message"):
            raise BadRequest("test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("Error: test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("[Error]: test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("Bad Request: test message")

    def test_timed_out(self):
        with pytest.raises(TimedOut, match="^Timed out$"):
            raise TimedOut

    def test_chat_migrated(self):
        with pytest.raises(ChatMigrated, match="Group migrated to supergroup. New chat id: 1234"):
            raise ChatMigrated(1234)
        try:
            raise ChatMigrated(1234)
        except ChatMigrated as e:
            assert e.new_chat_id == 1234

    def test_retry_after(self):
        with pytest.raises(RetryAfter, match="Flood control exceeded. Retry in 12.0 seconds"):
            raise RetryAfter(12)

    def test_conflict(self):
        with pytest.raises(Conflict, match='Something something.'):
            raise Conflict('Something something.')

    @pytest.mark.parametrize(
        "exception, attributes",
        [
            (TelegramError("test message"), ["message"]),
            (Unauthorized("test message"), ["message"]),
            (InvalidToken(), ["message"]),
            (NetworkError("test message"), ["message"]),
            (BadRequest("test message"), ["message"]),
            (TimedOut(), ["message"]),
            (ChatMigrated(1234), ["message", "new_chat_id"]),
            (RetryAfter(12), ["message", "retry_after"]),
            (Conflict("test message"), ["message"]),
            (TelegramDecryptionError("test message"), ["message"])
        ],
    )
    def test_errors_pickling(self, exception, attributes):
        print(exception)
        pickled = pickle.dumps(exception)
        unpickled = pickle.loads(pickled)
        assert type(unpickled) is type(exception)
        assert str(unpickled) == str(exception)

        for attribute in attributes:
            assert getattr(unpickled, attribute) == getattr(exception, attribute)

    def test_pickling_test_coverage(self):
        """
        This test is only here to make sure that new errors will override __reduce__ properly.
        Add the new error class to the below covered_subclasses dict, if it's covered in the above
        test_errors_pickling test.
        """
        def make_assertion(cls):
            assert {sc for sc in cls.__subclasses__()} == covered_subclasses[cls]
            for subcls in cls.__subclasses__():
                make_assertion(subcls)

        covered_subclasses = defaultdict(set)
        covered_subclasses.update({
            TelegramError: {Unauthorized, InvalidToken, NetworkError, ChatMigrated, RetryAfter,
                            Conflict, TelegramDecryptionError},
            NetworkError: {BadRequest, TimedOut}
        })

        make_assertion(TelegramError)
Example #20
0
class TestUpdater(object):
    message_count = 0
    received = None
    attempts = 0
    err_handler_called = Event()
    cb_handler_called = Event()

    @pytest.fixture(autouse=True)
    def reset(self):
        self.message_count = 0
        self.received = None
        self.attempts = 0
        self.err_handler_called.clear()
        self.cb_handler_called.clear()

    def error_handler(self, bot, update, error):
        self.received = error.message
        self.err_handler_called.set()

    def callback(self, bot, update):
        self.received = update.message.text
        self.cb_handler_called.set()

    # TODO: test clean= argument of Updater._bootstrap

    @pytest.mark.parametrize(('error',),
                             argvalues=[(TelegramError('Test Error 2'),),
                                        (Unauthorized('Test Unauthorized'),)],
                             ids=('TelegramError', 'Unauthorized'))
    def test_get_updates_normal_err(self, monkeypatch, updater, error):
        def test(*args, **kwargs):
            raise error

        monkeypatch.setattr(updater.bot, 'get_updates', test)
        monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
        updater.dispatcher.add_error_handler(self.error_handler)
        updater.start_polling(0.01)

        # Make sure that the error handler was called
        self.err_handler_called.wait()
        assert self.received == error.message

        # Make sure that Updater polling thread keeps running
        self.err_handler_called.clear()
        self.err_handler_called.wait()

    def test_get_updates_bailout_err(self, monkeypatch, updater, caplog):
        error = InvalidToken()

        def test(*args, **kwargs):
            raise error

        with caplog.at_level(logging.DEBUG):
            monkeypatch.setattr(updater.bot, 'get_updates', test)
            monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
            updater.dispatcher.add_error_handler(self.error_handler)
            updater.start_polling(0.01)
            assert self.err_handler_called.wait(1) is not True

        sleep(1)
        # NOTE: This test might hit a race condition and fail (though the 1 seconds delay above
        #       should work around it).
        # NOTE: Checking Updater.running is problematic because it is not set to False when there's
        #       an unhandled exception.
        # TODO: We should have a way to poll Updater status and decide if it's running or not.
        import pprint
        pprint.pprint([rec.getMessage() for rec in caplog.get_records('call')])
        assert any('unhandled exception in Bot:{}:updater'.format(updater.bot.id) in
                   rec.getMessage() for rec in caplog.get_records('call'))

    @pytest.mark.parametrize(('error',),
                             argvalues=[(RetryAfter(0.01),),
                                        (TimedOut(),)],
                             ids=('RetryAfter', 'TimedOut'))
    def test_get_updates_retries(self, monkeypatch, updater, error):
        event = Event()

        def test(*args, **kwargs):
            event.set()
            raise error

        monkeypatch.setattr(updater.bot, 'get_updates', test)
        monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
        updater.dispatcher.add_error_handler(self.error_handler)
        updater.start_polling(0.01)

        # Make sure that get_updates was called, but not the error handler
        event.wait()
        assert self.err_handler_called.wait(0.5) is not True
        assert self.received != error.message

        # Make sure that Updater polling thread keeps running
        event.clear()
        event.wait()
        assert self.err_handler_called.wait(0.5) is not True

    def test_webhook(self, monkeypatch, updater):
        q = Queue()
        monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(
            ip,
            port,
            url_path='TOKEN')
        sleep(.2)
        try:
            # Now, we send an update to the server via urlopen
            update = Update(1, message=Message(1, User(1, '', False), None, Chat(1, ''),
                                               text='Webhook'))
            self._send_webhook_msg(ip, port, update.to_json(), 'TOKEN')
            sleep(.2)
            assert q.get(False) == update

            # Returns 404 if path is incorrect
            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip, port, None, 'webookhandler.py')
            assert excinfo.value.code == 404

            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip, port, None, 'webookhandler.py',
                                       get_method=lambda: 'HEAD')
            assert excinfo.value.code == 404

            # Test multiple shutdown() calls
            updater.httpd.shutdown()
        finally:
            updater.httpd.shutdown()
            sleep(.2)
            assert not updater.httpd.is_running
            updater.stop()

    def test_webhook_ssl(self, monkeypatch, updater):
        monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        tg_err = False
        try:
            updater._start_webhook(
                ip,
                port,
                url_path='TOKEN',
                cert='./tests/test_updater.py',
                key='./tests/test_updater.py',
                bootstrap_retries=0,
                clean=False,
                webhook_url=None,
                allowed_updates=None)
        except TelegramError:
            tg_err = True
        assert tg_err

    def test_webhook_no_ssl(self, monkeypatch, updater):
        q = Queue()
        monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(ip, port, webhook_url=None)
        sleep(.2)

        # Now, we send an update to the server via urlopen
        update = Update(1, message=Message(1, User(1, '', False), None, Chat(1, ''),
                                           text='Webhook 2'))
        self._send_webhook_msg(ip, port, update.to_json())
        sleep(.2)
        assert q.get(False) == update
        updater.stop()

    def test_webhook_default_quote(self, monkeypatch, updater):
        updater._default_quote = True
        q = Queue()
        monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(
            ip,
            port,
            url_path='TOKEN')
        sleep(.2)

        # Now, we send an update to the server via urlopen
        update = Update(1, message=Message(1, User(1, '', False), None, Chat(1, ''),
                                           text='Webhook'))
        self._send_webhook_msg(ip, port, update.to_json(), 'TOKEN')
        sleep(.2)
        # assert q.get(False) == update
        assert q.get(False).message.default_quote is True
        updater.stop()

    @pytest.mark.skipif(not (sys.platform.startswith("win") and sys.version_info >= (3, 8)),
                        reason="only relevant on win with py>=3.8")
    def test_webhook_tornado_win_py38_workaround(self, updater, monkeypatch):
        updater._default_quote = True
        q = Queue()
        monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(
            ip,
            port,
            url_path='TOKEN')
        sleep(.2)

        try:
            from asyncio import (WindowsSelectorEventLoopPolicy)
        except ImportError:
            pass
            # not affected
        else:
            assert isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy)

        updater.stop()

    @pytest.mark.parametrize(('error',),
                             argvalues=[(TelegramError(''),)],
                             ids=('TelegramError',))
    def test_bootstrap_retries_success(self, monkeypatch, updater, error):
        retries = 2

        def attempt(*args, **kwargs):
            if self.attempts < retries:
                self.attempts += 1
                raise error

        monkeypatch.setattr(updater.bot, 'set_webhook', attempt)

        updater.running = True
        updater._bootstrap(retries, False, 'path', None, bootstrap_interval=0)
        assert self.attempts == retries

    @pytest.mark.parametrize(('error', 'attempts'),
                             argvalues=[(TelegramError(''), 2),
                                        (Unauthorized(''), 1),
                                        (InvalidToken(), 1)],
                             ids=('TelegramError', 'Unauthorized', 'InvalidToken'))
    def test_bootstrap_retries_error(self, monkeypatch, updater, error, attempts):
        retries = 1

        def attempt(*args, **kwargs):
            self.attempts += 1
            raise error

        monkeypatch.setattr(updater.bot, 'set_webhook', attempt)

        updater.running = True
        with pytest.raises(type(error)):
            updater._bootstrap(retries, False, 'path', None, bootstrap_interval=0)
        assert self.attempts == attempts

    @flaky(3, 1)
    def test_webhook_invalid_posts(self, updater):
        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # select random port for travis
        thr = Thread(
            target=updater._start_webhook,
            args=(ip, port, '', None, None, 0, False, None, None))
        thr.start()

        sleep(.2)

        try:
            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip, port, '<root><bla>data</bla></root>',
                                       content_type='application/xml')
            assert excinfo.value.code == 403

            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip, port, 'dummy-payload', content_len=-2)
            assert excinfo.value.code == 500

            # TODO: prevent urllib or the underlying from adding content-length
            # with pytest.raises(HTTPError) as excinfo:
            #     self._send_webhook_msg(ip, port, 'dummy-payload', content_len=None)
            # assert excinfo.value.code == 411

            with pytest.raises(HTTPError):
                self._send_webhook_msg(ip, port, 'dummy-payload', content_len='not-a-number')
            assert excinfo.value.code == 500

        finally:
            updater.httpd.shutdown()
            thr.join()

    def _send_webhook_msg(self,
                          ip,
                          port,
                          payload_str,
                          url_path='',
                          content_len=-1,
                          content_type='application/json',
                          get_method=None):
        headers = {'content-type': content_type, }

        if not payload_str:
            content_len = None
            payload = None
        else:
            payload = bytes(payload_str, encoding='utf-8')

        if content_len == -1:
            content_len = len(payload)

        if content_len is not None:
            headers['content-length'] = str(content_len)

        url = 'http://{ip}:{port}/{path}'.format(ip=ip, port=port, path=url_path)

        req = Request(url, data=payload, headers=headers)

        if get_method is not None:
            req.get_method = get_method

        return urlopen(req)

    def signal_sender(self, updater):
        sleep(0.2)
        while not updater.running:
            sleep(0.2)

        os.kill(os.getpid(), signal.SIGTERM)

    @signalskip
    def test_idle(self, updater, caplog):
        updater.start_polling(0.01)
        Thread(target=partial(self.signal_sender, updater=updater)).start()

        with caplog.at_level(logging.INFO):
            updater.idle()

        rec = caplog.records[-1]
        assert rec.msg.startswith('Received signal {}'.format(signal.SIGTERM))
        assert rec.levelname == 'INFO'

        # If we get this far, idle() ran through
        sleep(.5)
        assert updater.running is False

    @signalskip
    def test_user_signal(self, updater):
        temp_var = {'a': 0}

        def user_signal_inc(signum, frame):
            temp_var['a'] = 1

        updater.user_sig_handler = user_signal_inc
        updater.start_polling(0.01)
        Thread(target=partial(self.signal_sender, updater=updater)).start()
        updater.idle()
        # If we get this far, idle() ran through
        sleep(.5)
        assert updater.running is False
        assert temp_var['a'] != 0

    def test_create_bot(self):
        updater = Updater('123:abcd')
        assert updater.bot is not None

    def test_mutual_exclude_token_bot(self):
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(token='123:abcd', bot=bot)

    def test_no_token_or_bot_or_dispatcher(self):
        with pytest.raises(ValueError):
            Updater()

    def test_mutual_exclude_bot_private_key(self):
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(bot=bot, private_key=b'key')

    def test_mutual_exclude_bot_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(bot=bot, dispatcher=dispatcher)

    def test_mutual_exclude_persistence_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        persistence = BasePersistence()
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, persistence=persistence)

    def test_mutual_exclude_workers_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, workers=8)

    def test_mutual_exclude_use_context_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        use_context = not dispatcher.use_context
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, use_context=use_context)
 def mock(**_kw):
     raise TelegramError()
 def callback():
     raise TelegramError('Error')
Example #23
0
 def test(*args, **kwargs):
     raise TelegramError('test worked')
Example #24
0
 def attempt(_, *args, **kwargs):
     if self.attempts < retries:
         self.attempts += 1
         raise TelegramError('')
Example #25
0
class TestUpdater(object):
    message_count = 0
    received = None
    attempts = 0

    @pytest.fixture(autouse=True)
    def reset(self):
        self.message_count = 0
        self.received = None
        self.attempts = 0

    def error_handler(self, bot, update, error):
        self.received = error.message

    def callback(self, bot, update):
        self.received = update.message.text

    # TODO: test clean= argument

    def test_error_on_get_updates(self, monkeypatch, updater):
        def test(*args, **kwargs):
            raise TelegramError('Test Error 2')

        monkeypatch.setattr('telegram.Bot.get_updates', test)
        monkeypatch.setattr('telegram.Bot.set_webhook',
                            lambda *args, **kwargs: True)
        updater.dispatcher.add_error_handler(self.error_handler)
        updater.start_polling(0.01)
        sleep(.1)
        assert self.received == 'Test Error 2'

    def test_webhook(self, monkeypatch, updater):
        q = Queue()
        monkeypatch.setattr('telegram.Bot.set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.Bot.delete_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                            lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port for travis
        updater.start_webhook(
            ip,
            port,
            url_path='TOKEN',
            cert='./tests/test_updater.py',
            key='./tests/test_updater.py',
        )
        sleep(.2)
        # SSL-Wrapping will fail, so we start the server without SSL
        thr = Thread(target=updater.httpd.serve_forever)
        thr.start()

        try:
            # Now, we send an update to the server via urlopen
            update = Update(1,
                            message=Message(1,
                                            User(1, '', False),
                                            None,
                                            Chat(1, ''),
                                            text='Webhook'))
            self._send_webhook_msg(ip, port, update.to_json(), 'TOKEN')
            sleep(.2)
            assert q.get(False) == update

            response = self._send_webhook_msg(ip, port, None,
                                              'webookhandler.py')
            assert b'' == response.read()
            assert 200 == response.code

            response = self._send_webhook_msg(ip,
                                              port,
                                              None,
                                              'webookhandler.py',
                                              get_method=lambda: 'HEAD')

            assert b'' == response.read()
            assert 200 == response.code

            # Test multiple shutdown() calls
            updater.httpd.shutdown()
        finally:
            updater.httpd.shutdown()
            thr.join()

    def test_webhook_no_ssl(self, monkeypatch, updater):
        q = Queue()
        monkeypatch.setattr('telegram.Bot.set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.Bot.delete_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                            lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port for travis
        updater.start_webhook(ip, port, webhook_url=None)
        sleep(.2)

        # Now, we send an update to the server via urlopen
        update = Update(1,
                        message=Message(1,
                                        User(1, '', False),
                                        None,
                                        Chat(1, ''),
                                        text='Webhook 2'))
        self._send_webhook_msg(ip, port, update.to_json())
        sleep(.2)
        assert q.get(False) == update

    def test_bootstrap_retries_success(self, monkeypatch, updater):
        retries = 2

        def attempt(_, *args, **kwargs):
            if self.attempts < retries:
                self.attempts += 1
                raise TelegramError('')

        monkeypatch.setattr('telegram.Bot.set_webhook', attempt)

        updater._bootstrap(retries, False, 'path', None)
        assert self.attempts == retries

    @pytest.mark.parametrize(
        ('error', 'attempts'),
        argvalues=[(TelegramError(''), 2), (Unauthorized(''), 1),
                   (InvalidToken(), 1)],
        ids=('TelegramError', 'Unauthorized', 'InvalidToken'))
    def test_bootstrap_retries_error(self, monkeypatch, updater, error,
                                     attempts):
        retries = 1

        def attempt(_, *args, **kwargs):
            self.attempts += 1
            raise error

        monkeypatch.setattr('telegram.Bot.set_webhook', attempt)

        with pytest.raises(type(error)):
            updater._bootstrap(retries, False, 'path', None)
        assert self.attempts == attempts

    def test_webhook_invalid_posts(self, updater):
        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # select random port for travis
        thr = Thread(target=updater._start_webhook,
                     args=(ip, port, '', None, None, 0, False, None, None))
        thr.start()

        sleep(.2)

        try:
            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip,
                                       port,
                                       '<root><bla>data</bla></root>',
                                       content_type='application/xml')
            assert excinfo.value.code == 403

            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip,
                                       port,
                                       'dummy-payload',
                                       content_len=-2)
            assert excinfo.value.code == 403

            # TODO: prevent urllib or the underlying from adding content-length
            # with pytest.raises(HTTPError) as excinfo:
            #     self._send_webhook_msg(ip, port, 'dummy-payload', content_len=None)
            # assert excinfo.value.code == 411

            with pytest.raises(HTTPError):
                self._send_webhook_msg(ip,
                                       port,
                                       'dummy-payload',
                                       content_len='not-a-number')
            assert excinfo.value.code == 403

        finally:
            updater.httpd.shutdown()
            thr.join()

    def _send_webhook_msg(self,
                          ip,
                          port,
                          payload_str,
                          url_path='',
                          content_len=-1,
                          content_type='application/json',
                          get_method=None):
        headers = {
            'content-type': content_type,
        }

        if not payload_str:
            content_len = None
            payload = None
        else:
            payload = bytes(payload_str, encoding='utf-8')

        if content_len == -1:
            content_len = len(payload)

        if content_len is not None:
            headers['content-length'] = str(content_len)

        url = 'http://{ip}:{port}/{path}'.format(ip=ip,
                                                 port=port,
                                                 path=url_path)

        req = Request(url, data=payload, headers=headers)

        if get_method is not None:
            req.get_method = get_method

        return urlopen(req)

    def signal_sender(self, updater):
        sleep(0.2)
        while not updater.running:
            sleep(0.2)

        os.kill(os.getpid(), signal.SIGTERM)

    @signalskip
    def test_idle(self, updater, caplog):
        updater.start_polling(0.01)
        Thread(target=partial(self.signal_sender, updater=updater)).start()

        with caplog.at_level(logging.INFO):
            updater.idle()

        rec = caplog.records[-1]
        assert rec.msg.startswith('Received signal {}'.format(signal.SIGTERM))
        assert rec.levelname == 'INFO'

        # If we get this far, idle() ran through
        sleep(.5)
        assert updater.running is False

    @signalskip
    def test_user_signal(self, updater):
        temp_var = {'a': 0}

        def user_signal_inc(signum, frame):
            temp_var['a'] = 1

        updater.user_sig_handler = user_signal_inc
        updater.start_polling(0.01)
        Thread(target=partial(self.signal_sender, updater=updater)).start()
        updater.idle()
        # If we get this far, idle() ran through
        sleep(.5)
        assert updater.running is False
        assert temp_var['a'] != 0

    def test_create_bot(self):
        updater = Updater('123:abcd')
        assert updater.bot is not None

    def test_mutual_exclude_token_bot(self):
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(token='123:abcd', bot=bot)

    def test_no_token_or_bot(self):
        with pytest.raises(ValueError):
            Updater()
Example #26
0
 def callback_raise_error(self, bot, update):
     raise TelegramError(update.message.text)
Example #27
0
 def ticket_name(self, ticket_name: str):
     raise TelegramError("Direct setter for ticket name is denied")
class TestUpdater:
    message_count = 0
    received = None
    attempts = 0
    err_handler_called = Event()
    cb_handler_called = Event()
    offset = 0

    @pytest.fixture(autouse=True)
    def reset(self):
        self.message_count = 0
        self.received = None
        self.attempts = 0
        self.err_handler_called.clear()
        self.cb_handler_called.clear()

    def error_handler(self, bot, update, error):
        self.received = error.message
        self.err_handler_called.set()

    def callback(self, bot, update):
        self.received = update.message.text
        self.cb_handler_called.set()

    @pytest.mark.parametrize(('error', ),
                             argvalues=[(TelegramError('Test Error 2'), ),
                                        (Unauthorized('Test Unauthorized'), )],
                             ids=('TelegramError', 'Unauthorized'))
    def test_get_updates_normal_err(self, monkeypatch, updater, error):
        def test(*args, **kwargs):
            raise error

        monkeypatch.setattr(updater.bot, 'get_updates', test)
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        updater.dispatcher.add_error_handler(self.error_handler)
        updater.start_polling(0.01)

        # Make sure that the error handler was called
        self.err_handler_called.wait()
        assert self.received == error.message

        # Make sure that Updater polling thread keeps running
        self.err_handler_called.clear()
        self.err_handler_called.wait()

    def test_get_updates_bailout_err(self, monkeypatch, updater, caplog):
        error = InvalidToken()

        def test(*args, **kwargs):
            raise error

        with caplog.at_level(logging.DEBUG):
            monkeypatch.setattr(updater.bot, 'get_updates', test)
            monkeypatch.setattr(updater.bot, 'set_webhook',
                                lambda *args, **kwargs: True)
            updater.dispatcher.add_error_handler(self.error_handler)
            updater.start_polling(0.01)
            assert self.err_handler_called.wait(1) is not True

        sleep(1)
        # NOTE: This test might hit a race condition and fail (though the 1 seconds delay above
        #       should work around it).
        # NOTE: Checking Updater.running is problematic because it is not set to False when there's
        #       an unhandled exception.
        # TODO: We should have a way to poll Updater status and decide if it's running or not.
        import pprint
        pprint.pprint([rec.getMessage() for rec in caplog.get_records('call')])
        assert any('unhandled exception in Bot:{}:updater'.format(
            updater.bot.id) in rec.getMessage()
                   for rec in caplog.get_records('call'))

    @pytest.mark.parametrize(('error', ),
                             argvalues=[(RetryAfter(0.01), ), (TimedOut(), )],
                             ids=('RetryAfter', 'TimedOut'))
    def test_get_updates_retries(self, monkeypatch, updater, error):
        event = Event()

        def test(*args, **kwargs):
            event.set()
            raise error

        monkeypatch.setattr(updater.bot, 'get_updates', test)
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        updater.dispatcher.add_error_handler(self.error_handler)
        updater.start_polling(0.01)

        # Make sure that get_updates was called, but not the error handler
        event.wait()
        assert self.err_handler_called.wait(0.5) is not True
        assert self.received != error.message

        # Make sure that Updater polling thread keeps running
        event.clear()
        event.wait()
        assert self.err_handler_called.wait(0.5) is not True

    def test_webhook(self, monkeypatch, updater):
        q = Queue()
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                            lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(ip, port, url_path='TOKEN')
        sleep(.2)
        try:
            # Now, we send an update to the server via urlopen
            update = Update(1,
                            message=Message(1,
                                            None,
                                            Chat(1, ''),
                                            from_user=User(1, '', False),
                                            text='Webhook'))
            self._send_webhook_msg(ip, port, update.to_json(), 'TOKEN')
            sleep(.2)
            assert q.get(False) == update

            # Returns 404 if path is incorrect
            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip, port, None, 'webookhandler.py')
            assert excinfo.value.code == 404

            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip,
                                       port,
                                       None,
                                       'webookhandler.py',
                                       get_method=lambda: 'HEAD')
            assert excinfo.value.code == 404

            # Test multiple shutdown() calls
            updater.httpd.shutdown()
        finally:
            updater.httpd.shutdown()
            sleep(.2)
            assert not updater.httpd.is_running
            updater.stop()

    def test_start_webhook_no_warning_or_error_logs(self, caplog, updater,
                                                    monkeypatch):
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        # prevent api calls from @info decorator when updater.bot.id is used in thread names
        monkeypatch.setattr(updater.bot, 'bot',
                            User(id=123, first_name='bot', is_bot=True))
        monkeypatch.setattr(updater.bot, '_commands', [])

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        with caplog.at_level(logging.WARNING):
            updater.start_webhook(ip, port)
            updater.stop()
        assert not caplog.records

    @pytest.mark.skipif(
        os.name != 'nt' or sys.version_info < (3, 8),
        reason='Workaround only relevant on windows with py3.8+')
    def test_start_webhook_ensure_event_loop(self, updater, monkeypatch):
        def serve_forever(self, force_event_loop=False, ready=None):
            with self.server_lock:
                self.is_running = True
                self._ensure_event_loop(force_event_loop=force_event_loop)

                if ready is not None:
                    ready.set()

        monkeypatch.setattr(WebhookServer, 'serve_forever', serve_forever)
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port

        with set_asyncio_event_loop(None):
            updater._start_webhook(ip,
                                   port,
                                   url_path='TOKEN',
                                   cert=None,
                                   key=None,
                                   bootstrap_retries=0,
                                   clean=False,
                                   webhook_url=None,
                                   allowed_updates=None)

            assert isinstance(asyncio.get_event_loop(),
                              asyncio.SelectorEventLoop)

    @pytest.mark.skipif(
        os.name != 'nt' or sys.version_info < (3, 8),
        reason='Workaround only relevant on windows with py3.8+')
    def test_start_webhook_force_event_loop_false(self, updater, monkeypatch):
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port

        with set_asyncio_event_loop(asyncio.ProactorEventLoop()):
            with pytest.raises(TypeError,
                               match='`ProactorEventLoop` is incompatible'):
                updater._start_webhook(ip,
                                       port,
                                       url_path='TOKEN',
                                       cert=None,
                                       key=None,
                                       bootstrap_retries=0,
                                       clean=False,
                                       webhook_url=None,
                                       allowed_updates=None)

    @pytest.mark.skipif(
        os.name != 'nt' or sys.version_info < (3, 8),
        reason='Workaround only relevant on windows with py3.8+')
    def test_start_webhook_force_event_loop_true(self, updater, monkeypatch):
        def serve_forever(self, force_event_loop=False, ready=None):
            with self.server_lock:
                self.is_running = True
                self._ensure_event_loop(force_event_loop=force_event_loop)

                if ready is not None:
                    ready.set()

        monkeypatch.setattr(WebhookServer, 'serve_forever', serve_forever)
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port

        with set_asyncio_event_loop(asyncio.ProactorEventLoop()):
            updater._start_webhook(ip,
                                   port,
                                   url_path='TOKEN',
                                   cert=None,
                                   key=None,
                                   bootstrap_retries=0,
                                   clean=False,
                                   webhook_url=None,
                                   allowed_updates=None,
                                   force_event_loop=True)
            assert isinstance(asyncio.get_event_loop(),
                              asyncio.ProactorEventLoop)

    def test_webhook_ssl(self, monkeypatch, updater):
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        tg_err = False
        try:
            updater._start_webhook(ip,
                                   port,
                                   url_path='TOKEN',
                                   cert='./tests/test_updater.py',
                                   key='./tests/test_updater.py',
                                   bootstrap_retries=0,
                                   clean=False,
                                   webhook_url=None,
                                   allowed_updates=None)
        except TelegramError:
            tg_err = True
        assert tg_err

    def test_webhook_no_ssl(self, monkeypatch, updater):
        q = Queue()
        monkeypatch.setattr(updater.bot, 'set_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr(updater.bot, 'delete_webhook',
                            lambda *args, **kwargs: True)
        monkeypatch.setattr('telegram.ext.Dispatcher.process_update',
                            lambda _, u: q.put(u))

        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # Select random port
        updater.start_webhook(ip, port, webhook_url=None)
        sleep(.2)

        # Now, we send an update to the server via urlopen
        update = Update(1,
                        message=Message(1,
                                        None,
                                        Chat(1, ''),
                                        from_user=User(1, '', False),
                                        text='Webhook 2'))
        self._send_webhook_msg(ip, port, update.to_json())
        sleep(.2)
        assert q.get(False) == update
        updater.stop()

    @pytest.mark.parametrize(('error', ),
                             argvalues=[(TelegramError(''), )],
                             ids=('TelegramError', ))
    def test_bootstrap_retries_success(self, monkeypatch, updater, error):
        retries = 2

        def attempt(*args, **kwargs):
            if self.attempts < retries:
                self.attempts += 1
                raise error

        monkeypatch.setattr(updater.bot, 'set_webhook', attempt)

        updater.running = True
        updater._bootstrap(retries, False, 'path', None, bootstrap_interval=0)
        assert self.attempts == retries

    @pytest.mark.parametrize(
        ('error', 'attempts'),
        argvalues=[(TelegramError(''), 2), (Unauthorized(''), 1),
                   (InvalidToken(), 1)],
        ids=('TelegramError', 'Unauthorized', 'InvalidToken'))
    def test_bootstrap_retries_error(self, monkeypatch, updater, error,
                                     attempts):
        retries = 1

        def attempt(*args, **kwargs):
            self.attempts += 1
            raise error

        monkeypatch.setattr(updater.bot, 'set_webhook', attempt)

        updater.running = True
        with pytest.raises(type(error)):
            updater._bootstrap(retries,
                               False,
                               'path',
                               None,
                               bootstrap_interval=0)
        assert self.attempts == attempts

    def test_bootstrap_clean_updates(self, monkeypatch, updater):
        clean = True
        expected_id = 4
        self.offset = 0

        def get_updates(*args, **kwargs):
            # we're hitting this func twice
            # 1. no args, return list of updates
            # 2. with 1 arg, int => if int == expected_id => test successful

            # case 2
            # 2nd call from bootstrap____clean
            # we should be called with offset = 4
            # save value passed in self.offset for assert down below
            if len(args) > 0:
                self.offset = int(args[0])
                return []

            class FakeUpdate():
                def __init__(self, update_id):
                    self.update_id = update_id

            # case 1
            # return list of obj's

            # build list of fake updates
            # returns list of 4 objects with
            # update_id's 0, 1, 2 and 3
            return [FakeUpdate(i) for i in range(0, expected_id)]

        monkeypatch.setattr(updater.bot, 'get_updates', get_updates)

        updater.running = True
        updater._bootstrap(1, clean, None, None, bootstrap_interval=0)
        assert self.offset == expected_id

    @flaky(3, 1)
    def test_webhook_invalid_posts(self, updater):
        ip = '127.0.0.1'
        port = randrange(1024, 49152)  # select random port for travis
        thr = Thread(target=updater._start_webhook,
                     args=(ip, port, '', None, None, 0, False, None, None))
        thr.start()

        sleep(.2)

        try:
            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip,
                                       port,
                                       '<root><bla>data</bla></root>',
                                       content_type='application/xml')
            assert excinfo.value.code == 403

            with pytest.raises(HTTPError) as excinfo:
                self._send_webhook_msg(ip,
                                       port,
                                       'dummy-payload',
                                       content_len=-2)
            assert excinfo.value.code == 500

            # TODO: prevent urllib or the underlying from adding content-length
            # with pytest.raises(HTTPError) as excinfo:
            #     self._send_webhook_msg(ip, port, 'dummy-payload', content_len=None)
            # assert excinfo.value.code == 411

            with pytest.raises(HTTPError):
                self._send_webhook_msg(ip,
                                       port,
                                       'dummy-payload',
                                       content_len='not-a-number')
            assert excinfo.value.code == 500

        finally:
            updater.httpd.shutdown()
            thr.join()

    def _send_webhook_msg(self,
                          ip,
                          port,
                          payload_str,
                          url_path='',
                          content_len=-1,
                          content_type='application/json',
                          get_method=None):
        headers = {
            'content-type': content_type,
        }

        if not payload_str:
            content_len = None
            payload = None
        else:
            payload = bytes(payload_str, encoding='utf-8')

        if content_len == -1:
            content_len = len(payload)

        if content_len is not None:
            headers['content-length'] = str(content_len)

        url = 'http://{ip}:{port}/{path}'.format(ip=ip,
                                                 port=port,
                                                 path=url_path)

        req = Request(url, data=payload, headers=headers)

        if get_method is not None:
            req.get_method = get_method

        return urlopen(req)

    def signal_sender(self, updater):
        sleep(0.2)
        while not updater.running:
            sleep(0.2)

        os.kill(os.getpid(), signal.SIGTERM)

    @signalskip
    def test_idle(self, updater, caplog):
        updater.start_polling(0.01)
        Thread(target=partial(self.signal_sender, updater=updater)).start()

        with caplog.at_level(logging.INFO):
            updater.idle()

        rec = caplog.records[-2]
        assert rec.msg.startswith('Received signal {}'.format(signal.SIGTERM))
        assert rec.levelname == 'INFO'

        rec = caplog.records[-1]
        assert rec.msg.startswith('Scheduler has been shut down')
        assert rec.levelname == 'INFO'

        # If we get this far, idle() ran through
        sleep(.5)
        assert updater.running is False

    @signalskip
    def test_user_signal(self, updater):
        temp_var = {'a': 0}

        def user_signal_inc(signum, frame):
            temp_var['a'] = 1

        updater.user_sig_handler = user_signal_inc
        updater.start_polling(0.01)
        Thread(target=partial(self.signal_sender, updater=updater)).start()
        updater.idle()
        # If we get this far, idle() ran through
        sleep(.5)
        assert updater.running is False
        assert temp_var['a'] != 0

    def test_create_bot(self):
        updater = Updater('123:abcd')
        assert updater.bot is not None

    def test_mutual_exclude_token_bot(self):
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(token='123:abcd', bot=bot)

    def test_no_token_or_bot_or_dispatcher(self):
        with pytest.raises(ValueError):
            Updater()

    def test_mutual_exclude_bot_private_key(self):
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(bot=bot, private_key=b'key')

    def test_mutual_exclude_bot_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        bot = Bot('123:zyxw')
        with pytest.raises(ValueError):
            Updater(bot=bot, dispatcher=dispatcher)

    def test_mutual_exclude_persistence_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        persistence = DictPersistence()
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, persistence=persistence)

    def test_mutual_exclude_workers_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, workers=8)

    def test_mutual_exclude_use_context_dispatcher(self):
        dispatcher = Dispatcher(None, None)
        use_context = not dispatcher.use_context
        with pytest.raises(ValueError):
            Updater(dispatcher=dispatcher, use_context=use_context)

    def test_defaults_warning(self, bot):
        with pytest.warns(TelegramDeprecationWarning,
                          match='no effect when a Bot is passed'):
            Updater(bot=bot, defaults=Defaults())
Example #29
0
 def callback_raise_error(self, bot, update):
     if isinstance(bot, Bot):
         raise TelegramError(update.message.text)
     raise TelegramError(bot.message.text)
Example #30
0
 def ticket_base_price(self, ticket_base_price: str):
     raise TelegramError("Direct setter for ticket_base_price is denied")