def check_bot_permissions(channel: Chat) -> bool: try: member = channel.get_member(my_bot.me().id) if member.status == member.LEFT: raise Unauthorized('Not a member') except Unauthorized: raise Unauthorized('Not a member') return True
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 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_bootstrap_retries_unauth(self): retries = 3 self._setup_updater( '', messages=0, bootstrap_retries=retries, bootstrap_err=Unauthorized()) self.assertRaises(Unauthorized, self.updater._bootstrap, retries, False, 'path', None) self.assertEqual(self.updater.bot.bootstrap_attempts, 1)
def check_user_permissions(user: User, channel: Chat) -> bool: user_member: ChatMember = channel.get_member(user.id) if user_member.status not in [ user_member.ADMINISTRATOR, user_member.CREATOR ]: raise Unauthorized('User is not an admin of the channel.') return True
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 processar(self, bot, user, chat_id, args): membro = self.bd_cliente.get_membro(user.id) if self.is_group_chat(chat_id): if not membro: self.bd_cliente.insere_membro( user ) # TODO depois que ficar garantido que todos os usuários do grupo estão devidamente cadastrados, retirar essa inclusão automática elif not self.is_private_chat( user, chat_id) or not membro or membro['ativo'] == 0: raise Unauthorized(MSGS['unauthorized_error'])
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 Exception as e: log.error(str(e) + '\n' + traceback.format_exc()) """ 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(object): message_count = 0 received = None attempts = 0 @pytest.fixture(autouse=True) def reset(self): self.message_count = 0 self.received = None self.attempts = 0 def error_handler(self, bot, update, error): self.received = error.message def callback(self, bot, update): self.received = update.message.text # TODO: test clean= argument def test_error_on_get_updates(self, monkeypatch, updater): def test(*args, **kwargs): raise TelegramError('Test Error 2') monkeypatch.setattr('telegram.Bot.get_updates', test) monkeypatch.setattr('telegram.Bot.set_webhook', lambda *args, **kwargs: True) updater.dispatcher.add_error_handler(self.error_handler) updater.start_polling(0.01) sleep(.1) assert self.received == 'Test Error 2' def test_webhook(self, monkeypatch, updater): q = Queue() monkeypatch.setattr('telegram.Bot.set_webhook', lambda *args, **kwargs: True) monkeypatch.setattr('telegram.Bot.delete_webhook', lambda *args, **kwargs: True) monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u)) ip = '127.0.0.1' port = randrange(1024, 49152) # Select random port for travis updater.start_webhook( ip, port, url_path='TOKEN', cert='./tests/test_updater.py', key='./tests/test_updater.py', ) sleep(.2) # SSL-Wrapping will fail, so we start the server without SSL thr = Thread(target=updater.httpd.serve_forever) thr.start() try: # Now, we send an update to the server via urlopen update = Update(1, message=Message(1, User(1, '', False), None, Chat(1, ''), text='Webhook')) self._send_webhook_msg(ip, port, update.to_json(), 'TOKEN') sleep(.2) assert q.get(False) == update response = self._send_webhook_msg(ip, port, None, 'webookhandler.py') assert b'' == response.read() assert 200 == response.code response = self._send_webhook_msg(ip, port, None, 'webookhandler.py', get_method=lambda: 'HEAD') assert b'' == response.read() assert 200 == response.code # Test multiple shutdown() calls updater.httpd.shutdown() finally: updater.httpd.shutdown() thr.join() def test_webhook_no_ssl(self, monkeypatch, updater): q = Queue() monkeypatch.setattr('telegram.Bot.set_webhook', lambda *args, **kwargs: True) monkeypatch.setattr('telegram.Bot.delete_webhook', lambda *args, **kwargs: True) monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u)) ip = '127.0.0.1' port = randrange(1024, 49152) # Select random port for travis updater.start_webhook(ip, port, webhook_url=None) sleep(.2) # Now, we send an update to the server via urlopen update = Update(1, message=Message(1, User(1, '', False), None, Chat(1, ''), text='Webhook 2')) self._send_webhook_msg(ip, port, update.to_json()) sleep(.2) assert q.get(False) == update def test_bootstrap_retries_success(self, monkeypatch, updater): retries = 2 def attempt(_, *args, **kwargs): if self.attempts < retries: self.attempts += 1 raise TelegramError('') monkeypatch.setattr('telegram.Bot.set_webhook', attempt) updater._bootstrap(retries, False, 'path', None) assert self.attempts == retries @pytest.mark.parametrize( ('error', 'attempts'), argvalues=[(TelegramError(''), 2), (Unauthorized(''), 1), (InvalidToken(), 1)], ids=('TelegramError', 'Unauthorized', 'InvalidToken')) def test_bootstrap_retries_error(self, monkeypatch, updater, error, attempts): retries = 1 def attempt(_, *args, **kwargs): self.attempts += 1 raise error monkeypatch.setattr('telegram.Bot.set_webhook', attempt) with pytest.raises(type(error)): updater._bootstrap(retries, False, 'path', None) assert self.attempts == attempts def test_webhook_invalid_posts(self, updater): ip = '127.0.0.1' port = randrange(1024, 49152) # select random port for travis thr = Thread(target=updater._start_webhook, args=(ip, port, '', None, None, 0, False, None, None)) thr.start() sleep(.2) try: with pytest.raises(HTTPError) as excinfo: self._send_webhook_msg(ip, port, '<root><bla>data</bla></root>', content_type='application/xml') assert excinfo.value.code == 403 with pytest.raises(HTTPError) as excinfo: self._send_webhook_msg(ip, port, 'dummy-payload', content_len=-2) assert excinfo.value.code == 403 # TODO: prevent urllib or the underlying from adding content-length # with pytest.raises(HTTPError) as excinfo: # self._send_webhook_msg(ip, port, 'dummy-payload', content_len=None) # assert excinfo.value.code == 411 with pytest.raises(HTTPError): self._send_webhook_msg(ip, port, 'dummy-payload', content_len='not-a-number') assert excinfo.value.code == 403 finally: updater.httpd.shutdown() thr.join() def _send_webhook_msg(self, ip, port, payload_str, url_path='', content_len=-1, content_type='application/json', get_method=None): headers = { 'content-type': content_type, } if not payload_str: content_len = None payload = None else: payload = bytes(payload_str, encoding='utf-8') if content_len == -1: content_len = len(payload) if content_len is not None: headers['content-length'] = str(content_len) url = 'http://{ip}:{port}/{path}'.format(ip=ip, port=port, path=url_path) req = Request(url, data=payload, headers=headers) if get_method is not None: req.get_method = get_method return urlopen(req) def signal_sender(self, updater): sleep(0.2) while not updater.running: sleep(0.2) os.kill(os.getpid(), signal.SIGTERM) @signalskip def test_idle(self, updater, caplog): updater.start_polling(0.01) Thread(target=partial(self.signal_sender, updater=updater)).start() with caplog.at_level(logging.INFO): updater.idle() rec = caplog.records[-1] assert rec.msg.startswith('Received signal {}'.format(signal.SIGTERM)) assert rec.levelname == 'INFO' # If we get this far, idle() ran through sleep(.5) assert updater.running is False @signalskip def test_user_signal(self, updater): temp_var = {'a': 0} def user_signal_inc(signum, frame): temp_var['a'] = 1 updater.user_sig_handler = user_signal_inc updater.start_polling(0.01) Thread(target=partial(self.signal_sender, updater=updater)).start() updater.idle() # If we get this far, idle() ran through sleep(.5) assert updater.running is False assert temp_var['a'] != 0 def test_create_bot(self): updater = Updater('123:abcd') assert updater.bot is not None def test_mutual_exclude_token_bot(self): bot = Bot('123:zyxw') with pytest.raises(ValueError): Updater(token='123:abcd', bot=bot) def test_no_token_or_bot(self): with pytest.raises(ValueError): Updater()
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())
def app_unknown_unauthorized_user(mocker, app): app_ = copy(app) app_.updater = MagicMock() app_.updater.bot.send_message = MagicMock(side_effect=Unauthorized("something unknown")) return app_
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 = 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())
def test_send_message_unauthorized(): bot = MagicMock() bot.send_message.side_effect = Unauthorized("err") assert send_message(bot, text="hey foo!") == "err"