def _request(self, method, path): kwargs = { 'method': method, 'url': f"{self._base_url}{path}", 'headers': { 'Cookie': f'iksm_session={self.iksm_session}; path=/; ' f'domain=.app.splatoon2.nintendo.net;', 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } } try: resp = self._con_pool.request(**kwargs) except urllib3.exceptions.TimeoutError: raise TimedOut() except urllib3.exceptions.HTTPError as error: raise NetworkError('urllib3 HTTPError {0}'.format(error)) if 200 <= resp.status <= 299: return resp.data elif resp.status == 403: raise Splatoon2SessionInvalid() else: message = 'Unknown HTTPError' raise NetworkError('{0} ({1})'.format(message, resp.status))
def decorator(*args, **kwargs): try: return func(*args, **kwargs) except HTTPError as error: # `HTTPError` inherits from `URLError` so `HTTPError` handling must # come first. errcode = error.getcode() if errcode in (401, 403): raise Unauthorized() if errcode == 502: raise NetworkError('Bad Gateway') try: message = _parse(error.read()) except ValueError: message = 'Unknown HTTPError {0}'.format(error.getcode()) raise NetworkError('{0} ({1})'.format(message, errcode)) except URLError as error: raise NetworkError('URLError: {0!r}'.format(error)) except (SSLError, socket.timeout) as error: err_s = str(error) if "operation timed out" in err_s: raise TimedOut() raise NetworkError(err_s) except HTTPException as error: raise NetworkError('HTTPException: {0!r}'.format(error))
def test_process_file_with_no_running_state(self, downloader_cls, running_context, update_context): upd, context = update_context tracker = MagicMock() downloader = Mock() downloader_cls.return_value = downloader running_context.return_value.__enter__.return_value = tracker tracker.running_state = False thumb = BytesIO() file = BytesIO() downloader.get_downloaded.return_value = Downloaded( 'title', 'author', file, 0, thumb) process_file(upd, context) context.bot.send_audio.assert_called_with('callback_chat_id', file, 0, 'author', 'title', timeout=1000, thumb=thumb) tracker.update.assert_called() tracker.reset_mock() context.bot.send_audio.side_effect = TimedOut() process_file(upd, context) context.bot.send_message.assert_called_with( 'callback_chat_id', text=resp.SENDING_ERROR('callback_link')) tracker.update.assert_not_called() tracker.retrieve_waiting.assert_called() context.update_queue.put.assert_called()
def _request_wrapper(self, *args, **kwargs): """Wraps urllib3 request for handling known exceptions. Args: args: unnamed arguments, passed to urllib3 request. kwargs: keyword arguments, passed tp urllib3 request. Returns: str: A non-parsed JSON text. Raises: TelegramError """ # Make sure to hint Telegram servers that we reuse connections by sending # "Connection: keep-alive" in the HTTP headers. if 'headers' not in kwargs: kwargs['headers'] = {} kwargs['headers']['connection'] = 'keep-alive' # Also set our user agent kwargs['headers']['user-agent'] = USER_AGENT try: resp = self._con_pool.request(*args, **kwargs) except urllib3.exceptions.TimeoutError: raise TimedOut() except urllib3.exceptions.HTTPError as error: # HTTPError must come last as its the base urllib3 exception class # TODO: do something smart here; for now just raise NetworkError raise NetworkError('urllib3 HTTPError {}'.format(error)) if 200 <= resp.status <= 299: # 200-299 range are HTTP success statuses return resp.data try: message = self._parse(resp.data) except ValueError: message = 'Unknown HTTPError' if resp.status in (401, 403): raise Unauthorized(message) elif resp.status == 400: raise BadRequest(message) elif resp.status == 404: raise InvalidToken() elif resp.status == 409: raise Conflict(message) elif resp.status == 413: raise NetworkError( 'File too large. Check telegram api limits ' 'https://core.telegram.org/bots/api#senddocument') elif resp.status == 502: raise NetworkError('Bad Gateway') else: raise NetworkError('{} ({})'.format(message, resp.status))
def _request_wrapper(self, *args, **kwargs): """Wraps urllib3 request for handling known exceptions. Args: args: unnamed arguments, passed to urllib3 request. kwargs: keyword arguments, passed tp urllib3 request. Returns: str: A non-parsed JSON text. Raises: TelegramError """ try: resp = self._con_pool.request(*args, **kwargs) except urllib3.exceptions.TimeoutError: raise TimedOut() except urllib3.exceptions.HTTPError as error: # HTTPError must come last as its the base urllib3 exception class # TODO: do something smart here; for now just raise NetworkError raise NetworkError('urllib3 HTTPError {0}'.format(error)) if 200 <= resp.status <= 299: # 200-299 range are HTTP success statuses return resp.data try: message = self._parse(resp.data) except ValueError: raise NetworkError('Unknown HTTPError {0}'.format(resp.status)) if resp.status in (401, 403): raise Unauthorized() elif resp.status == 400: raise BadRequest(repr(message)) elif resp.status == 404: raise InvalidToken() elif resp.status == 502: raise NetworkError('Bad Gateway') else: raise NetworkError('{0} ({1})'.format(message, resp.status))
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)
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())
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(Forbidden, match="test message"): raise Forbidden("test message") with pytest.raises(Forbidden, match="^Test message$"): raise Forbidden("Error: test message") with pytest.raises(Forbidden, match="^Test message$"): raise Forbidden("[Error]: test message") with pytest.raises(Forbidden, match="^Test message$"): raise Forbidden("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 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"]), (Forbidden("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"]), (PassportDecryptionError("test message"), ["message"]), (InvalidCallbackData("test data"), ["callback_data"]), ], ) def test_errors_pickling(self, exception, attributes): 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) @pytest.mark.parametrize( "inst", [ (TelegramError("test message")), (Forbidden("test message")), (InvalidToken()), (NetworkError("test message")), (BadRequest("test message")), (TimedOut()), (ChatMigrated(1234)), (RetryAfter(12)), (Conflict("test message")), (PassportDecryptionError("test message")), (InvalidCallbackData("test data")), ], ) def test_slot_behaviour(self, inst, mro_slots): for attr in inst.__slots__: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(inst)) == len(set( mro_slots(inst))), "duplicate slot" def test_coverage(self): """ This test is only here to make sure that new errors will override __reduce__ and set __slots__ properly. Add the new error class to the below covered_subclasses dict, if it's covered in the above test_errors_pickling and test_slots_behavior tests. """ def make_assertion(cls): assert set(cls.__subclasses__()) == covered_subclasses[cls] for subcls in cls.__subclasses__(): make_assertion(subcls) covered_subclasses = defaultdict(set) covered_subclasses.update({ TelegramError: { Forbidden, InvalidToken, NetworkError, ChatMigrated, RetryAfter, Conflict, PassportDecryptionError, InvalidCallbackData, }, NetworkError: {BadRequest, TimedOut}, }) make_assertion(TelegramError) def test_string_representations(self): """We just randomly test a few of the subclasses - should suffice""" e = TelegramError("This is a message") assert repr(e) == "TelegramError('This is a message')" assert str(e) == "This is a message" e = RetryAfter(42) assert repr( e) == "RetryAfter('Flood control exceeded. Retry in 42 seconds')" assert str(e) == "Flood control exceeded. Retry in 42 seconds" e = BadRequest("This is a message") assert repr(e) == "BadRequest('This is a message')" assert str(e) == "This is a message"
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)
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())
class TestUpdater: message_count = 0 received = None attempts = 0 err_handler_called = None cb_handler_called = None offset = 0 test_flag = False @pytest.fixture(autouse=True) def reset(self): self.message_count = 0 self.received = None self.attempts = 0 self.err_handler_called = None self.cb_handler_called = None self.test_flag = False def error_callback(self, error): self.received = error self.err_handler_called.set() def callback(self, update, context): self.received = update.message.text self.cb_handler_called.set() async def test_slot_behaviour(self, updater, mro_slots): async with updater: 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 len(mro_slots(updater)) == len(set( mro_slots(updater))), "duplicate slot" def test_init(self, bot): queue = asyncio.Queue() updater = Updater(bot=bot, update_queue=queue) assert updater.bot is bot assert updater.update_queue is queue async def test_initialize(self, bot, monkeypatch): async def initialize_bot(*args, **kwargs): self.test_flag = True async with make_bot(token=bot.token) as test_bot: monkeypatch.setattr(test_bot, "initialize", initialize_bot) updater = Updater(bot=test_bot, update_queue=asyncio.Queue()) await updater.initialize() assert self.test_flag async def test_shutdown(self, bot, monkeypatch): async def shutdown_bot(*args, **kwargs): self.test_flag = True async with make_bot(token=bot.token) as test_bot: monkeypatch.setattr(test_bot, "shutdown", shutdown_bot) updater = Updater(bot=test_bot, update_queue=asyncio.Queue()) await updater.initialize() await updater.shutdown() assert self.test_flag async def test_multiple_inits_and_shutdowns(self, updater, monkeypatch): self.test_flag = defaultdict(int) async def initialize(*args, **kargs): self.test_flag["init"] += 1 async def shutdown(*args, **kwargs): self.test_flag["shutdown"] += 1 monkeypatch.setattr(updater.bot, "initialize", initialize) monkeypatch.setattr(updater.bot, "shutdown", shutdown) await updater.initialize() await updater.initialize() await updater.initialize() await updater.shutdown() await updater.shutdown() await updater.shutdown() assert self.test_flag["init"] == 1 assert self.test_flag["shutdown"] == 1 async def test_multiple_init_cycles(self, updater): # nothing really to assert - this should just not fail async with updater: await updater.bot.get_me() async with updater: await updater.bot.get_me() @pytest.mark.parametrize("method", ["start_polling", "start_webhook"]) async def test_start_without_initialize(self, updater, method): with pytest.raises(RuntimeError, match="not initialized"): await getattr(updater, method)() @pytest.mark.parametrize("method", ["start_polling", "start_webhook"]) async def test_shutdown_while_running(self, updater, method, monkeypatch): async def set_webhook(*args, **kwargs): return True monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port async with updater: if "webhook" in method: await getattr(updater, method)( ip_address=ip, port=port, ) else: await getattr(updater, method)() with pytest.raises(RuntimeError, match="still running"): await updater.shutdown() await updater.stop() async def test_context_manager(self, monkeypatch, updater): async def initialize(*args, **kwargs): self.test_flag = ["initialize"] async def shutdown(*args, **kwargs): self.test_flag.append("stop") monkeypatch.setattr(Updater, "initialize", initialize) monkeypatch.setattr(Updater, "shutdown", shutdown) async with updater: pass assert self.test_flag == ["initialize", "stop"] async def test_context_manager_exception_on_init(self, monkeypatch, updater): async def initialize(*args, **kwargs): raise RuntimeError("initialize") async def shutdown(*args): self.test_flag = "stop" monkeypatch.setattr(Updater, "initialize", initialize) monkeypatch.setattr(Updater, "shutdown", shutdown) with pytest.raises(RuntimeError, match="initialize"): async with updater: pass assert self.test_flag == "stop" @pytest.mark.parametrize("drop_pending_updates", (True, False)) async def test_polling_basic(self, monkeypatch, updater, drop_pending_updates): updates = asyncio.Queue() await updates.put(Update(update_id=1)) await updates.put(Update(update_id=2)) async def get_updates(*args, **kwargs): next_update = await updates.get() updates.task_done() return [next_update] orig_del_webhook = updater.bot.delete_webhook async def delete_webhook(*args, **kwargs): # Dropping pending updates is done by passing the parameter to delete_webhook if kwargs.get("drop_pending_updates"): self.message_count += 1 return await orig_del_webhook(*args, **kwargs) monkeypatch.setattr(updater.bot, "get_updates", get_updates) monkeypatch.setattr(updater.bot, "delete_webhook", delete_webhook) async with updater: return_value = await updater.start_polling( drop_pending_updates=drop_pending_updates) assert return_value is updater.update_queue assert updater.running await updates.join() await updater.stop() assert not updater.running assert not (await updater.bot.get_webhook_info()).url if drop_pending_updates: assert self.message_count == 1 else: assert self.message_count == 0 await updates.put(Update(update_id=3)) await updates.put(Update(update_id=4)) # We call the same logic twice to make sure that restarting the updater works as well await updater.start_polling( drop_pending_updates=drop_pending_updates) assert updater.running await updates.join() await updater.stop() assert not updater.running assert not (await updater.bot.get_webhook_info()).url self.received = [] self.message_count = 0 while not updater.update_queue.empty(): update = updater.update_queue.get_nowait() self.message_count += 1 self.received.append(update.update_id) assert self.message_count == 4 assert self.received == [1, 2, 3, 4] async def test_start_polling_already_running(self, updater): async with updater: await updater.start_polling() task = asyncio.create_task(updater.start_polling()) with pytest.raises(RuntimeError, match="already running"): await task await updater.stop() with pytest.raises(RuntimeError, match="not running"): await updater.stop() async def test_start_polling_get_updates_parameters( self, updater, monkeypatch): update_queue = asyncio.Queue() await update_queue.put(Update(update_id=1)) expected = dict( timeout=10, read_timeout=2, write_timeout=DEFAULT_NONE, connect_timeout=DEFAULT_NONE, pool_timeout=DEFAULT_NONE, allowed_updates=None, api_kwargs=None, ) async def get_updates(*args, **kwargs): for key, value in expected.items(): assert kwargs.pop(key, None) == value offset = kwargs.pop("offset", None) # Check that we don't get any unexpected kwargs assert kwargs == {} if offset is not None and self.message_count != 0: assert offset == self.message_count + 1, "get_updates got wrong `offset` parameter" update = await update_queue.get() self.message_count = update.update_id update_queue.task_done() return [update] monkeypatch.setattr(updater.bot, "get_updates", get_updates) async with updater: await updater.start_polling() await update_queue.join() await updater.stop() expected = dict( timeout=42, read_timeout=43, write_timeout=44, connect_timeout=45, pool_timeout=46, allowed_updates=["message"], api_kwargs=None, ) await update_queue.put(Update(update_id=2)) await updater.start_polling( timeout=42, read_timeout=43, write_timeout=44, connect_timeout=45, pool_timeout=46, allowed_updates=["message"], ) await update_queue.join() await updater.stop() @pytest.mark.parametrize("exception_class", (InvalidToken, TelegramError)) @pytest.mark.parametrize("retries", (3, 0)) async def test_start_polling_bootstrap_retries(self, updater, monkeypatch, exception_class, retries): async def do_request(*args, **kwargs): self.message_count += 1 raise exception_class(str(self.message_count)) async with updater: # Patch within the context so that updater.bot.initialize can still be called # by the context manager monkeypatch.setattr(HTTPXRequest, "do_request", do_request) if exception_class == InvalidToken: with pytest.raises(InvalidToken, match="1"): await updater.start_polling(bootstrap_retries=retries) else: with pytest.raises(TelegramError, match=str(retries + 1)): await updater.start_polling(bootstrap_retries=retries) @pytest.mark.parametrize( "error,callback_should_be_called", argvalues=[ (TelegramError("TestMessage"), True), (RetryAfter(1), False), (TimedOut("TestMessage"), False), ], ids=("TelegramError", "RetryAfter", "TimedOut"), ) @pytest.mark.parametrize("custom_error_callback", [True, False]) async def test_start_polling_exceptions_and_error_callback( self, monkeypatch, updater, error, callback_should_be_called, custom_error_callback, caplog): get_updates_event = asyncio.Event() async def get_updates(*args, **kwargs): # So that the main task has a chance to be called await asyncio.sleep(0) get_updates_event.set() raise error monkeypatch.setattr(updater.bot, "get_updates", get_updates) monkeypatch.setattr(updater.bot, "set_webhook", lambda *args, **kwargs: True) with pytest.raises( TypeError, match="`error_callback` must not be a coroutine function"): await updater.start_polling(error_callback=get_updates) async with updater: self.err_handler_called = asyncio.Event() with caplog.at_level(logging.ERROR): if custom_error_callback: await updater.start_polling( error_callback=self.error_callback) else: await updater.start_polling() # Also makes sure that the error handler was called await get_updates_event.wait() if callback_should_be_called: # Make sure that the error handler was called if custom_error_callback: assert self.received == error else: assert len(caplog.records) > 0 records = (record.getMessage() for record in caplog.records) assert "Error while getting Updates: TestMessage" in records # Make sure that get_updates was called assert get_updates_event.is_set() # Make sure that Updater polling keeps running self.err_handler_called.clear() get_updates_event.clear() caplog.clear() # Also makes sure that the error handler was called await get_updates_event.wait() if callback_should_be_called: if callback_should_be_called: if custom_error_callback: assert self.received == error else: assert len(caplog.records) > 0 records = (record.getMessage() for record in caplog.records) assert "Error while getting Updates: TestMessage" in records await updater.stop() async def test_start_polling_unexpected_shutdown(self, updater, monkeypatch, caplog): update_queue = asyncio.Queue() await update_queue.put(Update(update_id=1)) await update_queue.put(Update(update_id=2)) first_update_event = asyncio.Event() second_update_event = asyncio.Event() async def get_updates(*args, **kwargs): self.message_count = kwargs.get("offset") update = await update_queue.get() if update.update_id == 1: first_update_event.set() else: await second_update_event.wait() return [update] monkeypatch.setattr(updater.bot, "get_updates", get_updates) async with updater: with caplog.at_level(logging.ERROR): await updater.start_polling() await first_update_event.wait() # Unfortunately we need to use the private attribute here to produce the problem updater._running = False second_update_event.set() await asyncio.sleep(0.1) assert caplog.records records = (record.getMessage() for record in caplog.records) assert any("Updater stopped unexpectedly." in record for record in records) # Make sure that the update_id offset wasn't increased assert self.message_count == 2 async def test_start_polling_not_running_after_failure( self, updater, monkeypatch): # Unfortunately we have to use some internal logic to trigger an exception async def _start_polling(*args, **kwargs): raise Exception("Test Exception") monkeypatch.setattr(Updater, "_start_polling", _start_polling) async with updater: with pytest.raises(Exception, match="Test Exception"): await updater.start_polling() assert updater.running is False async def test_polling_update_de_json_fails(self, monkeypatch, updater, caplog): updates = asyncio.Queue() raise_exception = True await updates.put(Update(update_id=1)) async def get_updates(*args, **kwargs): if raise_exception: await asyncio.sleep(0.01) raise TypeError("Invalid Data") next_update = await updates.get() updates.task_done() return [next_update] orig_del_webhook = updater.bot.delete_webhook async def delete_webhook(*args, **kwargs): # Dropping pending updates is done by passing the parameter to delete_webhook if kwargs.get("drop_pending_updates"): self.message_count += 1 return await orig_del_webhook(*args, **kwargs) monkeypatch.setattr(updater.bot, "get_updates", get_updates) monkeypatch.setattr(updater.bot, "delete_webhook", delete_webhook) async with updater: with caplog.at_level(logging.CRITICAL): await updater.start_polling() assert updater.running await asyncio.sleep(1) assert len(caplog.records) > 0 for record in caplog.records: assert record.getMessage().startswith( "Something went wrong processing") # Make sure that everything works fine again when receiving proper updates raise_exception = False await asyncio.sleep(0.5) caplog.clear() with caplog.at_level(logging.CRITICAL): await updates.join() assert len(caplog.records) == 0 await updater.stop() assert not updater.running @pytest.mark.parametrize("ext_bot", [True, False]) @pytest.mark.parametrize("drop_pending_updates", (True, False)) @pytest.mark.parametrize("secret_token", ["SecretToken", None]) async def test_webhook_basic(self, monkeypatch, updater, drop_pending_updates, ext_bot, secret_token): # 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 = DictBot(updater.bot.token) async def delete_webhook(*args, **kwargs): # Dropping pending updates is done by passing the parameter to delete_webhook if kwargs.get("drop_pending_updates"): self.message_count += 1 return True async def set_webhook(*args, **kwargs): return True monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) monkeypatch.setattr(updater.bot, "delete_webhook", delete_webhook) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port async with updater: return_value = await updater.start_webhook( drop_pending_updates=drop_pending_updates, ip_address=ip, port=port, url_path="TOKEN", secret_token=secret_token, ) assert return_value is updater.update_queue assert updater.running # Now, we send an update to the server update = make_message_update("Webhook") await send_webhook_message(ip, port, update.to_json(), "TOKEN", secret_token=secret_token) assert (await updater.update_queue.get()).to_dict() == update.to_dict() # Returns Not Found if path is incorrect response = await send_webhook_message(ip, port, "123456", "webhook_handler.py") assert response.status_code == HTTPStatus.NOT_FOUND # Returns METHOD_NOT_ALLOWED if method is not allowed response = await send_webhook_message(ip, port, None, "TOKEN", get_method="HEAD") assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED if secret_token: # Returns Forbidden if no secret token is set response_text = "<html><title>403: {0}</title><body>403: {0}</body></html>" response = await send_webhook_message(ip, port, update.to_json(), "TOKEN") assert response.status_code == HTTPStatus.FORBIDDEN assert response.text == response_text.format( "Request did not include the secret token") # Returns Forbidden if the secret token is wrong response = await send_webhook_message( ip, port, update.to_json(), "TOKEN", secret_token="NotTheSecretToken") assert response.status_code == HTTPStatus.FORBIDDEN assert response.text == response_text.format( "Request had the wrong secret token") await updater.stop() assert not updater.running if drop_pending_updates: assert self.message_count == 1 else: assert self.message_count == 0 # We call the same logic twice to make sure that restarting the updater works as well await updater.start_webhook( drop_pending_updates=drop_pending_updates, ip_address=ip, port=port, url_path="TOKEN", ) assert updater.running update = make_message_update("Webhook") await send_webhook_message(ip, port, update.to_json(), "TOKEN") assert (await updater.update_queue.get()).to_dict() == update.to_dict() await updater.stop() assert not updater.running async def test_start_webhook_already_running(self, updater, monkeypatch): async def return_true(*args, **kwargs): return True monkeypatch.setattr(updater.bot, "set_webhook", return_true) monkeypatch.setattr(updater.bot, "delete_webhook", return_true) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port async with updater: await updater.start_webhook(ip, port, url_path="TOKEN") task = asyncio.create_task( updater.start_webhook(ip, port, url_path="TOKEN")) with pytest.raises(RuntimeError, match="already running"): await task await updater.stop() with pytest.raises(RuntimeError, match="not running"): await updater.stop() async def test_start_webhook_parameters_passing(self, updater, monkeypatch): expected_delete_webhook = dict(drop_pending_updates=None, ) expected_set_webhook = dict( certificate=None, max_connections=40, allowed_updates=None, ip_address=None, secret_token=None, **expected_delete_webhook, ) async def set_webhook(*args, **kwargs): for key, value in expected_set_webhook.items(): assert kwargs.pop(key, None) == value, f"set, {key}, {value}" assert kwargs in ( { "url": "http://127.0.0.1:80/" }, { "url": "http://*****:*****@pytest.mark.parametrize("invalid_data", [True, False], ids=("invalid data", "valid data")) async def test_webhook_arbitrary_callback_data(self, monkeypatch, updater, invalid_data, chat_id): """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 async def return_true(*args, **kwargs): return True try: monkeypatch.setattr(updater.bot, "set_webhook", return_true) monkeypatch.setattr(updater.bot, "delete_webhook", return_true) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port async with updater: await updater.start_webhook(ip, port, url_path="TOKEN") # Now, we send an update to the server 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) update = make_message_update( message="test_webhook_arbitrary_callback_data", message_factory=make_message, reply_markup=reply_markup, user=updater.bot.bot, ) await send_webhook_message(ip, port, update.to_json(), "TOKEN") received_update = await updater.update_queue.get() assert received_update.update_id == update.update_id message_dict = update.message.to_dict() received_dict = received_update.message.to_dict() message_dict.pop("reply_markup") received_dict.pop("reply_markup") assert message_dict == received_dict 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" await 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() async def test_webhook_invalid_ssl(self, monkeypatch, updater): async def return_true(*args, **kwargs): return True monkeypatch.setattr(updater.bot, "set_webhook", return_true) monkeypatch.setattr(updater.bot, "delete_webhook", return_true) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port async with updater: with pytest.raises(TelegramError, match="Invalid SSL"): await updater.start_webhook( ip, port, url_path="TOKEN", cert=Path(__file__).as_posix(), key=Path(__file__).as_posix(), bootstrap_retries=0, drop_pending_updates=False, webhook_url=None, allowed_updates=None, ) assert updater.running is False async def test_webhook_ssl_just_for_telegram(self, monkeypatch, updater): """Here we just test that the SSL info is pased to Telegram, but __not__ to the the webhook server""" async def set_webhook(**kwargs): self.test_flag.append(bool(kwargs.get("certificate"))) return True async def return_true(*args, **kwargs): return True orig_wh_server_init = WebhookServer.__init__ def webhook_server_init(*args, **kwargs): self.test_flag = [kwargs.get("ssl_ctx") is None] orig_wh_server_init(*args, **kwargs) monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) monkeypatch.setattr(updater.bot, "delete_webhook", return_true) monkeypatch.setattr( "telegram.ext._utils.webhookhandler.WebhookServer.__init__", webhook_server_init) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port async with updater: await updater.start_webhook(ip, port, webhook_url=None, cert=Path(__file__).as_posix()) # Now, we send an update to the server update = make_message_update(message="test_message") await send_webhook_message(ip, port, update.to_json()) assert (await updater.update_queue.get()).to_dict() == update.to_dict() assert self.test_flag == [True, True] await updater.stop() @pytest.mark.parametrize("exception_class", (InvalidToken, TelegramError)) @pytest.mark.parametrize("retries", (3, 0)) async def test_start_webhook_bootstrap_retries(self, updater, monkeypatch, exception_class, retries): async def do_request(*args, **kwargs): self.message_count += 1 raise exception_class(str(self.message_count)) async with updater: # Patch within the context so that updater.bot.initialize can still be called # by the context manager monkeypatch.setattr(HTTPXRequest, "do_request", do_request) if exception_class == InvalidToken: with pytest.raises(InvalidToken, match="1"): await updater.start_webhook(bootstrap_retries=retries) else: with pytest.raises(TelegramError, match=str(retries + 1)): await updater.start_webhook(bootstrap_retries=retries, ) async def test_webhook_invalid_posts(self, updater, monkeypatch): async def return_true(*args, **kwargs): return True monkeypatch.setattr(updater.bot, "set_webhook", return_true) monkeypatch.setattr(updater.bot, "delete_webhook", return_true) ip = "127.0.0.1" port = randrange(1024, 49152) async with updater: await updater.start_webhook(listen=ip, port=port) response = await send_webhook_message(ip, port, None, content_type="invalid") assert response.status_code == HTTPStatus.FORBIDDEN response = await send_webhook_message( ip, port, payload_str="<root><bla>data</bla></root>", content_type="application/xml", ) assert response.status_code == HTTPStatus.FORBIDDEN response = await send_webhook_message(ip, port, "dummy-payload", content_len=None) assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR # httpx already complains about bad content length in _send_webhook_message # before the requests below reach the webhook, but not testing this is probably # okay # response = await send_webhook_message( # ip, port, 'dummy-payload', content_len=-2) # assert response.status_code == HTTPStatus.FORBIDDEN # response = await send_webhook_message( # ip, port, 'dummy-payload', content_len='not-a-number') # assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR await updater.stop() async def test_webhook_update_de_json_fails(self, monkeypatch, updater, caplog): async def delete_webhook(*args, **kwargs): return True async def set_webhook(*args, **kwargs): return True def de_json_fails(*args, **kwargs): raise TypeError("Invalid input") monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) monkeypatch.setattr(updater.bot, "delete_webhook", delete_webhook) orig_de_json = Update.de_json monkeypatch.setattr(Update, "de_json", de_json_fails) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port async with updater: return_value = await updater.start_webhook( ip_address=ip, port=port, url_path="TOKEN", ) assert return_value is updater.update_queue assert updater.running # Now, we send an update to the server update = make_message_update("Webhook") with caplog.at_level(logging.CRITICAL): await send_webhook_message(ip, port, update.to_json(), "TOKEN") assert len(caplog.records) == 1 assert caplog.records[-1].getMessage().startswith( "Something went wrong processing") # Make sure that everything works fine again when receiving proper updates caplog.clear() with caplog.at_level(logging.CRITICAL): monkeypatch.setattr(Update, "de_json", orig_de_json) await send_webhook_message(ip, port, update.to_json(), "TOKEN") assert ( await updater.update_queue.get()).to_dict() == update.to_dict() assert len(caplog.records) == 0 await updater.stop() assert not updater.running
def __call__(self, **kwargs): if self.n_times_timeout > 0: self.n_times_timeout -= 1 raise TimedOut() else: return 1
class TestUpdater: message_count = 0 received = None attempts = 0 err_handler_called = Event() cb_handler_called = Event() offset = 0 test_flag = False @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() @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 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(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() 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(('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() 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() assert len(recwarn) == 2 for msg in recwarn: print(msg) 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() 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): 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())
async def do_request( self, url: str, method: str, request_data: RequestData = None, read_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, write_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, connect_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, pool_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, ) -> Tuple[int, bytes]: """See :meth:`BaseRequest.do_request`.""" if self._client.is_closed: raise RuntimeError("This HTTPXRequest is not initialized!") # If user did not specify timeouts (for e.g. in a bot method), use the default ones when we # created this instance. if isinstance(read_timeout, DefaultValue): read_timeout = self._client.timeout.read if isinstance(write_timeout, DefaultValue): write_timeout = self._client.timeout.write if isinstance(connect_timeout, DefaultValue): connect_timeout = self._client.timeout.connect if isinstance(pool_timeout, DefaultValue): pool_timeout = self._client.timeout.pool timeout = httpx.Timeout( connect=connect_timeout, read=read_timeout, write=write_timeout, pool=pool_timeout, ) # TODO p0: On Linux, use setsockopt to properly set socket level keepalive. # (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 120) # (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 30) # (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 8) # TODO p4: Support setsockopt on lesser platforms than Linux. files = request_data.multipart_data if request_data else None data = request_data.json_parameters if request_data else None try: res = await self._client.request( method=method, url=url, headers={"User-Agent": self.USER_AGENT}, timeout=timeout, files=files, data=data, ) except httpx.TimeoutException as err: if isinstance(err, httpx.PoolTimeout): raise TimedOut(message=( "Pool timeout: All connections in the connection pool are occupied. " "Request was *not* sent to Telegram. Consider adjusting the connection " "pool size or the pool timeout.")) from err raise TimedOut from err except httpx.HTTPError as err: # HTTPError must come last as its the base httpx exception class # TODO p4: do something smart here; for now just raise NetworkError raise NetworkError(f"httpx HTTPError: {err}") from err return res.status_code, res.content