2015-11-06 00:23:34 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
|
|
|
# A library that provides a Python interface to the Telegram Bot API
|
2022-01-03 08:15:18 +01:00
|
|
|
# Copyright (C) 2015-2022
|
2016-01-05 14:12:03 +01:00
|
|
|
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
2015-11-06 00:23:34 +01:00
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
2017-08-11 23:58:41 +02:00
|
|
|
# it under the terms of the GNU Lesser Public License as published by
|
2015-11-06 00:23:34 +01:00
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2017-08-11 23:58:41 +02:00
|
|
|
# GNU Lesser Public License for more details.
|
2015-11-06 00:23:34 +01:00
|
|
|
#
|
2017-08-11 23:58:41 +02:00
|
|
|
# You should have received a copy of the GNU Lesser Public License
|
2015-11-06 00:23:34 +01:00
|
|
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
2020-08-25 22:21:24 +02:00
|
|
|
import asyncio
|
2018-01-09 16:54:07 +01:00
|
|
|
import logging
|
2017-08-11 23:58:41 +02:00
|
|
|
import os
|
2015-11-24 14:57:54 +01:00
|
|
|
import signal
|
2016-04-24 04:11:25 +02:00
|
|
|
import sys
|
2020-08-25 22:21:24 +02:00
|
|
|
import threading
|
|
|
|
from contextlib import contextmanager
|
|
|
|
|
2020-02-07 17:32:38 +01:00
|
|
|
from flaky import flaky
|
2018-01-09 16:54:07 +01:00
|
|
|
from functools import partial
|
2016-09-06 15:38:07 +02:00
|
|
|
from queue import Queue
|
2016-04-27 00:28:21 +02:00
|
|
|
from random import randrange
|
2018-03-02 22:11:16 +01:00
|
|
|
from threading import Thread, Event
|
2017-08-11 23:58:41 +02:00
|
|
|
from time import sleep
|
2015-11-06 00:23:34 +01:00
|
|
|
|
2020-06-15 18:20:51 +02:00
|
|
|
from urllib.request import Request, urlopen
|
|
|
|
from urllib.error import HTTPError
|
2015-11-06 00:23:34 +01:00
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
import pytest
|
2021-08-19 22:01:10 +02:00
|
|
|
from .conftest import DictBot
|
2015-11-06 00:23:34 +01:00
|
|
|
|
2021-06-06 11:48:48 +02:00
|
|
|
from telegram import (
|
|
|
|
Message,
|
|
|
|
User,
|
|
|
|
Chat,
|
|
|
|
Update,
|
|
|
|
Bot,
|
|
|
|
InlineKeyboardMarkup,
|
|
|
|
InlineKeyboardButton,
|
|
|
|
)
|
2021-09-22 16:49:10 +02:00
|
|
|
from telegram.error import Unauthorized, InvalidToken, TimedOut, RetryAfter, TelegramError
|
2021-06-06 11:48:48 +02:00
|
|
|
from telegram.ext import (
|
|
|
|
InvalidCallbackData,
|
|
|
|
ExtBot,
|
2021-10-09 13:56:50 +02:00
|
|
|
Updater,
|
|
|
|
UpdaterBuilder,
|
|
|
|
DispatcherBuilder,
|
2021-06-06 11:48:48 +02:00
|
|
|
)
|
2021-01-30 14:15:39 +01:00
|
|
|
from telegram.ext.utils.webhookhandler import WebhookServer
|
2015-11-06 00:23:34 +01:00
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
signalskip = pytest.mark.skipif(
|
|
|
|
sys.platform == 'win32',
|
2021-05-19 13:33:41 +02:00
|
|
|
reason="Can't send signals without stopping whole process on windows",
|
2020-10-09 17:22:07 +02:00
|
|
|
)
|
2016-09-06 15:38:07 +02:00
|
|
|
|
|
|
|
|
2020-08-25 22:21:24 +02:00
|
|
|
ASYNCIO_LOCK = threading.Lock()
|
|
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def set_asyncio_event_loop(loop):
|
|
|
|
with ASYNCIO_LOCK:
|
|
|
|
try:
|
|
|
|
orig_lop = asyncio.get_event_loop()
|
|
|
|
except RuntimeError:
|
|
|
|
orig_lop = None
|
|
|
|
asyncio.set_event_loop(loop)
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
asyncio.set_event_loop(orig_lop)
|
2020-03-09 22:13:16 +01:00
|
|
|
|
|
|
|
|
2020-06-15 18:20:51 +02:00
|
|
|
class TestUpdater:
|
2017-08-11 23:58:41 +02:00
|
|
|
message_count = 0
|
|
|
|
received = None
|
|
|
|
attempts = 0
|
2018-03-02 22:11:16 +01:00
|
|
|
err_handler_called = Event()
|
|
|
|
cb_handler_called = Event()
|
2020-06-24 00:25:58 +02:00
|
|
|
offset = 0
|
2021-03-13 15:35:26 +01:00
|
|
|
test_flag = False
|
2015-11-21 15:45:45 +01:00
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
@pytest.fixture(autouse=True)
|
2015-11-24 20:34:38 +01:00
|
|
|
def reset(self):
|
|
|
|
self.message_count = 0
|
2017-08-11 23:58:41 +02:00
|
|
|
self.received = None
|
|
|
|
self.attempts = 0
|
2018-03-02 22:11:16 +01:00
|
|
|
self.err_handler_called.clear()
|
|
|
|
self.cb_handler_called.clear()
|
2021-03-13 15:35:26 +01:00
|
|
|
self.test_flag = False
|
2016-04-21 13:07:44 +02:00
|
|
|
|
2021-08-29 18:15:04 +02:00
|
|
|
def error_handler(self, update, context):
|
|
|
|
self.received = context.error.message
|
2018-03-02 22:11:16 +01:00
|
|
|
self.err_handler_called.set()
|
2015-11-21 19:35:24 +01:00
|
|
|
|
2021-08-29 18:15:04 +02:00
|
|
|
def callback(self, update, context):
|
2017-08-11 23:58:41 +02:00
|
|
|
self.received = update.message.text
|
2018-03-02 22:11:16 +01:00
|
|
|
self.cb_handler_called.set()
|
2016-09-06 15:38:07 +02:00
|
|
|
|
2021-10-09 13:56:50 +02:00
|
|
|
def test_slot_behaviour(self, updater, mro_slots):
|
|
|
|
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_manual_init_warning(self, recwarn):
|
|
|
|
Updater(
|
|
|
|
bot=None,
|
|
|
|
dispatcher=None,
|
|
|
|
update_queue=None,
|
|
|
|
exception_event=None,
|
|
|
|
user_signal_handler=None,
|
|
|
|
)
|
2021-06-06 11:48:48 +02:00
|
|
|
assert len(recwarn) == 1
|
2021-10-09 13:56:50 +02:00
|
|
|
assert (
|
|
|
|
str(recwarn[-1].message)
|
|
|
|
== '`Updater` instances should be built via the `UpdaterBuilder`.'
|
|
|
|
)
|
|
|
|
assert recwarn[0].filename == __file__, "stacklevel is incorrect!"
|
|
|
|
|
|
|
|
def test_builder(self, updater):
|
|
|
|
builder_1 = updater.builder()
|
|
|
|
builder_2 = updater.builder()
|
|
|
|
assert isinstance(builder_1, UpdaterBuilder)
|
|
|
|
assert isinstance(builder_2, UpdaterBuilder)
|
|
|
|
assert builder_1 is not builder_2
|
|
|
|
|
|
|
|
# Make sure that setting a token doesn't raise an exception
|
|
|
|
# i.e. check that the builders are "empty"/new
|
|
|
|
builder_1.token(updater.bot.token)
|
|
|
|
builder_2.token(updater.bot.token)
|
2021-06-06 11:48:48 +02:00
|
|
|
|
2021-09-20 08:45:42 +02:00
|
|
|
def test_warn_con_pool(self, bot, recwarn, dp):
|
2021-10-09 13:56:50 +02:00
|
|
|
DispatcherBuilder().bot(bot).workers(5).build()
|
|
|
|
UpdaterBuilder().bot(bot).workers(8).build()
|
|
|
|
UpdaterBuilder().bot(bot).workers(2).build()
|
2021-09-20 08:45:42 +02:00
|
|
|
assert len(recwarn) == 2
|
2021-10-09 13:56:50 +02:00
|
|
|
for idx, value in enumerate((9, 12)):
|
|
|
|
warning = (
|
|
|
|
'The Connection pool of Request object is smaller (8) than the '
|
|
|
|
f'recommended value of {value}.'
|
|
|
|
)
|
2021-09-20 08:45:42 +02:00
|
|
|
assert str(recwarn[idx].message) == warning
|
|
|
|
assert recwarn[idx].filename == __file__, "wrong stacklevel!"
|
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
('error',),
|
|
|
|
argvalues=[(TelegramError('Test Error 2'),), (Unauthorized('Test Unauthorized'),)],
|
|
|
|
ids=('TelegramError', 'Unauthorized'),
|
|
|
|
)
|
2018-03-02 22:11:16 +01:00
|
|
|
def test_get_updates_normal_err(self, monkeypatch, updater, error):
|
2017-08-11 23:58:41 +02:00
|
|
|
def test(*args, **kwargs):
|
2018-03-02 22:11:16 +01:00
|
|
|
raise error
|
|
|
|
|
2020-02-06 11:22:56 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'get_updates', test)
|
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
2018-03-02 22:11:16 +01:00
|
|
|
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()
|
|
|
|
|
2021-02-13 22:07:37 +01:00
|
|
|
@pytest.mark.filterwarnings('ignore:.*:pytest.PytestUnhandledThreadExceptionWarning')
|
2018-03-02 22:11:16 +01:00
|
|
|
def test_get_updates_bailout_err(self, monkeypatch, updater, caplog):
|
|
|
|
error = InvalidToken()
|
|
|
|
|
|
|
|
def test(*args, **kwargs):
|
|
|
|
raise error
|
|
|
|
|
|
|
|
with caplog.at_level(logging.DEBUG):
|
2020-02-06 11:22:56 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'get_updates', test)
|
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
2018-03-02 22:11:16 +01:00
|
|
|
updater.dispatcher.add_error_handler(self.error_handler)
|
|
|
|
updater.start_polling(0.01)
|
2018-09-07 16:05:45 +02:00
|
|
|
assert self.err_handler_called.wait(1) is not True
|
2018-03-02 22:11:16 +01:00
|
|
|
|
2018-10-02 09:00:38 +02:00
|
|
|
sleep(1)
|
|
|
|
# NOTE: This test might hit a race condition and fail (though the 1 seconds delay above
|
2018-03-02 22:11:16 +01:00
|
|
|
# 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.
|
2019-08-23 22:57:08 +02:00
|
|
|
import pprint
|
2018-03-02 22:11:16 +01:00
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
pprint.pprint([rec.getMessage() for rec in caplog.get_records('call')])
|
|
|
|
assert any(
|
2020-11-23 22:09:29 +01:00
|
|
|
f'unhandled exception in Bot:{updater.bot.id}:updater' in rec.getMessage()
|
2020-10-09 17:22:07 +02:00
|
|
|
for rec in caplog.get_records('call')
|
|
|
|
)
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
('error',), argvalues=[(RetryAfter(0.01),), (TimedOut(),)], ids=('RetryAfter', 'TimedOut')
|
|
|
|
)
|
2018-03-02 22:11:16 +01:00
|
|
|
def test_get_updates_retries(self, monkeypatch, updater, error):
|
|
|
|
event = Event()
|
|
|
|
|
|
|
|
def test(*args, **kwargs):
|
|
|
|
event.set()
|
|
|
|
raise error
|
2016-08-06 14:47:45 +02:00
|
|
|
|
2020-02-06 11:22:56 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'get_updates', test)
|
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
2017-08-11 23:58:41 +02:00
|
|
|
updater.dispatcher.add_error_handler(self.error_handler)
|
|
|
|
updater.start_polling(0.01)
|
2018-03-02 22:11:16 +01:00
|
|
|
|
|
|
|
# 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
|
2016-08-06 14:47:45 +02:00
|
|
|
|
2021-06-06 11:48:48 +02:00
|
|
|
@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:
|
2021-08-19 22:01:10 +02:00
|
|
|
updater.bot = DictBot(updater.bot.token)
|
2021-06-06 11:48:48 +02:00
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
q = Queue()
|
2020-02-06 11:22:56 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
|
|
|
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
|
2017-08-11 23:58:41 +02:00
|
|
|
monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u))
|
2015-11-21 23:22:41 +01:00
|
|
|
|
2016-02-19 16:37:38 +01:00
|
|
|
ip = '127.0.0.1'
|
2020-02-23 22:04:56 +01:00
|
|
|
port = randrange(1024, 49152) # Select random port
|
2020-10-09 17:22:07 +02:00
|
|
|
updater.start_webhook(ip, port, url_path='TOKEN')
|
|
|
|
sleep(0.2)
|
2017-08-11 23:58:41 +02:00
|
|
|
try:
|
|
|
|
# Now, we send an update to the server via urlopen
|
2020-10-09 17:22:07 +02:00
|
|
|
update = Update(
|
|
|
|
1,
|
|
|
|
message=Message(
|
|
|
|
1, None, Chat(1, ''), from_user=User(1, '', False), text='Webhook'
|
|
|
|
),
|
|
|
|
)
|
2017-08-11 23:58:41 +02:00
|
|
|
self._send_webhook_msg(ip, port, update.to_json(), 'TOKEN')
|
2020-10-09 17:22:07 +02:00
|
|
|
sleep(0.2)
|
2017-08-11 23:58:41 +02:00
|
|
|
assert q.get(False) == update
|
2015-11-21 21:22:58 +01:00
|
|
|
|
2018-09-08 22:25:48 +02:00
|
|
|
# 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
|
2015-11-24 19:35:02 +01:00
|
|
|
|
2018-09-08 22:25:48 +02:00
|
|
|
with pytest.raises(HTTPError) as excinfo:
|
2020-10-09 17:22:07 +02:00
|
|
|
self._send_webhook_msg(
|
|
|
|
ip, port, None, 'webookhandler.py', get_method=lambda: 'HEAD'
|
|
|
|
)
|
2018-09-08 22:25:48 +02:00
|
|
|
assert excinfo.value.code == 404
|
2015-11-24 19:35:02 +01:00
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
# Test multiple shutdown() calls
|
|
|
|
updater.httpd.shutdown()
|
|
|
|
finally:
|
|
|
|
updater.httpd.shutdown()
|
2020-10-09 17:22:07 +02:00
|
|
|
sleep(0.2)
|
2018-09-08 22:25:48 +02:00
|
|
|
assert not updater.httpd.is_running
|
|
|
|
updater.stop()
|
|
|
|
|
2021-06-06 11:48:48 +02:00
|
|
|
@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()
|
|
|
|
|
2021-10-09 13:56:50 +02:00
|
|
|
@pytest.mark.parametrize('use_dispatcher', (True, False))
|
|
|
|
def test_start_webhook_no_warning_or_error_logs(
|
|
|
|
self, caplog, updater, monkeypatch, use_dispatcher
|
|
|
|
):
|
|
|
|
if not use_dispatcher:
|
|
|
|
updater.dispatcher = None
|
|
|
|
|
|
|
|
self.test_flag = 0
|
|
|
|
|
|
|
|
def set_flag():
|
|
|
|
self.test_flag += 1
|
|
|
|
|
2020-08-25 22:21:24 +02:00
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
|
|
|
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
|
2021-10-09 13:56:50 +02:00
|
|
|
monkeypatch.setattr(updater.bot._request, 'stop', lambda *args, **kwargs: set_flag())
|
2020-08-25 22:21:24 +02:00
|
|
|
# prevent api calls from @info decorator when updater.bot.id is used in thread names
|
2021-01-17 09:23:36 +01:00
|
|
|
monkeypatch.setattr(updater.bot, '_bot', User(id=123, first_name='bot', is_bot=True))
|
2020-08-25 22:21:24 +02:00
|
|
|
|
|
|
|
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
|
2021-10-09 13:56:50 +02:00
|
|
|
# Make sure that bot.request.stop() has been called exactly once
|
|
|
|
assert self.test_flag == 1
|
2020-08-25 22:21:24 +02:00
|
|
|
|
2018-09-08 22:25:48 +02:00
|
|
|
def test_webhook_ssl(self, monkeypatch, updater):
|
2020-02-06 11:22:56 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
|
|
|
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
|
2018-09-08 22:25:48 +02:00
|
|
|
ip = '127.0.0.1'
|
2020-02-23 22:04:56 +01:00
|
|
|
port = randrange(1024, 49152) # Select random port
|
2018-09-08 22:25:48 +02:00
|
|
|
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,
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates=False,
|
2018-09-08 22:25:48 +02:00
|
|
|
webhook_url=None,
|
2020-10-09 17:22:07 +02:00
|
|
|
allowed_updates=None,
|
|
|
|
)
|
2018-09-08 22:25:48 +02:00
|
|
|
except TelegramError:
|
|
|
|
tg_err = True
|
|
|
|
assert tg_err
|
2015-11-24 19:35:02 +01:00
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
def test_webhook_no_ssl(self, monkeypatch, updater):
|
|
|
|
q = Queue()
|
2020-02-06 11:22:56 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
|
|
|
|
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
|
2017-08-11 23:58:41 +02:00
|
|
|
monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u))
|
2015-11-30 23:00:56 +01:00
|
|
|
|
2016-02-19 16:37:38 +01:00
|
|
|
ip = '127.0.0.1'
|
2020-02-23 22:04:56 +01:00
|
|
|
port = randrange(1024, 49152) # Select random port
|
2017-08-11 23:58:41 +02:00
|
|
|
updater.start_webhook(ip, port, webhook_url=None)
|
2020-10-09 17:22:07 +02:00
|
|
|
sleep(0.2)
|
2015-11-30 23:00:56 +01:00
|
|
|
|
|
|
|
# Now, we send an update to the server via urlopen
|
2020-10-09 17:22:07 +02:00
|
|
|
update = Update(
|
|
|
|
1,
|
|
|
|
message=Message(1, None, Chat(1, ''), from_user=User(1, '', False), text='Webhook 2'),
|
|
|
|
)
|
2016-02-19 16:37:38 +01:00
|
|
|
self._send_webhook_msg(ip, port, update.to_json())
|
2020-10-09 17:22:07 +02:00
|
|
|
sleep(0.2)
|
2017-08-11 23:58:41 +02:00
|
|
|
assert q.get(False) == update
|
2018-09-08 22:25:48 +02:00
|
|
|
updater.stop()
|
2017-08-11 23:58:41 +02:00
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
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]
|
|
|
|
|
2021-06-06 09:27:45 +02:00
|
|
|
@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
|
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
@pytest.mark.parametrize(('error',), argvalues=[(TelegramError(''),)], ids=('TelegramError',))
|
2018-03-02 22:11:16 +01:00
|
|
|
def test_bootstrap_retries_success(self, monkeypatch, updater, error):
|
2017-08-11 23:58:41 +02:00
|
|
|
retries = 2
|
|
|
|
|
2020-02-06 11:22:56 +01:00
|
|
|
def attempt(*args, **kwargs):
|
2017-08-11 23:58:41 +02:00
|
|
|
if self.attempts < retries:
|
|
|
|
self.attempts += 1
|
2018-03-02 22:11:16 +01:00
|
|
|
raise error
|
2017-08-11 23:58:41 +02:00
|
|
|
|
2020-02-06 11:22:56 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', attempt)
|
2017-08-11 23:58:41 +02:00
|
|
|
|
2018-03-02 22:11:16 +01:00
|
|
|
updater.running = True
|
|
|
|
updater._bootstrap(retries, False, 'path', None, bootstrap_interval=0)
|
2017-08-11 23:58:41 +02:00
|
|
|
assert self.attempts == retries
|
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
('error', 'attempts'),
|
|
|
|
argvalues=[(TelegramError(''), 2), (Unauthorized(''), 1), (InvalidToken(), 1)],
|
|
|
|
ids=('TelegramError', 'Unauthorized', 'InvalidToken'),
|
|
|
|
)
|
2017-08-11 23:58:41 +02:00
|
|
|
def test_bootstrap_retries_error(self, monkeypatch, updater, error, attempts):
|
2016-03-14 00:36:01 +01:00
|
|
|
retries = 1
|
|
|
|
|
2020-02-06 11:22:56 +01:00
|
|
|
def attempt(*args, **kwargs):
|
2017-08-11 23:58:41 +02:00
|
|
|
self.attempts += 1
|
|
|
|
raise error
|
|
|
|
|
2020-02-06 11:22:56 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'set_webhook', attempt)
|
2016-03-14 00:36:01 +01:00
|
|
|
|
2018-03-02 22:11:16 +01:00
|
|
|
updater.running = True
|
2017-08-11 23:58:41 +02:00
|
|
|
with pytest.raises(type(error)):
|
2018-03-02 22:11:16 +01:00
|
|
|
updater._bootstrap(retries, False, 'path', None, bootstrap_interval=0)
|
2017-08-11 23:58:41 +02:00
|
|
|
assert self.attempts == attempts
|
2016-02-19 16:37:38 +01:00
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
@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
|
2020-06-24 00:25:58 +02:00
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
def delete_webhook(**kwargs):
|
|
|
|
self.test_flag = kwargs.get('drop_pending_updates') == drop_pending_updates
|
2020-06-24 00:25:58 +02:00
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
monkeypatch.setattr(updater.bot, 'delete_webhook', delete_webhook)
|
2020-06-24 00:25:58 +02:00
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
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
|
2020-06-24 00:25:58 +02:00
|
|
|
|
2020-02-07 17:32:38 +01:00
|
|
|
@flaky(3, 1)
|
2017-08-11 23:58:41 +02:00
|
|
|
def test_webhook_invalid_posts(self, updater):
|
2016-02-19 16:37:38 +01:00
|
|
|
ip = '127.0.0.1'
|
|
|
|
port = randrange(1024, 49152) # select random port for travis
|
2016-08-26 11:17:05 +02:00
|
|
|
thr = Thread(
|
2020-10-09 17:22:07 +02:00
|
|
|
target=updater._start_webhook, args=(ip, port, '', None, None, 0, False, None, None)
|
|
|
|
)
|
2016-02-19 16:37:38 +01:00
|
|
|
thr.start()
|
2015-11-30 23:00:56 +01:00
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
sleep(0.2)
|
2016-02-19 16:37:38 +01:00
|
|
|
|
|
|
|
try:
|
2017-08-11 23:58:41 +02:00
|
|
|
with pytest.raises(HTTPError) as excinfo:
|
2020-10-09 17:22:07 +02:00
|
|
|
self._send_webhook_msg(
|
|
|
|
ip, port, '<root><bla>data</bla></root>', content_type='application/xml'
|
|
|
|
)
|
2017-08-11 23:58:41 +02:00
|
|
|
assert excinfo.value.code == 403
|
2016-02-19 16:37:38 +01:00
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
with pytest.raises(HTTPError) as excinfo:
|
2016-05-15 02:52:35 +02:00
|
|
|
self._send_webhook_msg(ip, port, 'dummy-payload', content_len=-2)
|
2018-09-08 22:25:48 +02:00
|
|
|
assert excinfo.value.code == 500
|
2016-02-19 16:37:38 +01:00
|
|
|
|
|
|
|
# TODO: prevent urllib or the underlying from adding content-length
|
2017-08-11 23:58:41 +02:00
|
|
|
# with pytest.raises(HTTPError) as excinfo:
|
|
|
|
# self._send_webhook_msg(ip, port, 'dummy-payload', content_len=None)
|
|
|
|
# assert excinfo.value.code == 411
|
2016-02-19 16:37:38 +01:00
|
|
|
|
2018-02-19 11:41:38 +01:00
|
|
|
with pytest.raises(HTTPError):
|
2016-05-15 02:52:35 +02:00
|
|
|
self._send_webhook_msg(ip, port, 'dummy-payload', content_len='not-a-number')
|
2018-09-08 22:25:48 +02:00
|
|
|
assert excinfo.value.code == 500
|
2016-02-19 16:37:38 +01:00
|
|
|
|
|
|
|
finally:
|
2017-08-11 23:58:41 +02:00
|
|
|
updater.httpd.shutdown()
|
2016-02-19 16:37:38 +01:00
|
|
|
thr.join()
|
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
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,
|
|
|
|
}
|
2015-11-30 23:00:56 +01:00
|
|
|
|
2016-02-19 16:37:38 +01:00
|
|
|
if not payload_str:
|
|
|
|
content_len = None
|
|
|
|
payload = None
|
|
|
|
else:
|
|
|
|
payload = bytes(payload_str, encoding='utf-8')
|
2015-11-30 23:00:56 +01:00
|
|
|
|
2016-02-19 16:37:38 +01:00
|
|
|
if content_len == -1:
|
|
|
|
content_len = len(payload)
|
|
|
|
|
|
|
|
if content_len is not None:
|
|
|
|
headers['content-length'] = str(content_len)
|
|
|
|
|
2020-11-23 22:09:29 +01:00
|
|
|
url = f'http://{ip}:{port}/{url_path}'
|
2016-02-19 16:37:38 +01:00
|
|
|
|
|
|
|
req = Request(url, data=payload, headers=headers)
|
|
|
|
|
|
|
|
if get_method is not None:
|
|
|
|
req.get_method = get_method
|
|
|
|
|
|
|
|
return urlopen(req)
|
2015-11-30 23:00:56 +01:00
|
|
|
|
2018-01-09 16:54:07 +01:00
|
|
|
def signal_sender(self, updater):
|
2017-08-11 23:58:41 +02:00
|
|
|
sleep(0.2)
|
2018-01-09 16:54:07 +01:00
|
|
|
while not updater.running:
|
|
|
|
sleep(0.2)
|
|
|
|
|
2015-11-24 14:57:54 +01:00
|
|
|
os.kill(os.getpid(), signal.SIGTERM)
|
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
@signalskip
|
2018-01-09 16:54:07 +01:00
|
|
|
def test_idle(self, updater, caplog):
|
2017-08-11 23:58:41 +02:00
|
|
|
updater.start_polling(0.01)
|
2018-01-09 16:54:07 +01:00
|
|
|
Thread(target=partial(self.signal_sender, updater=updater)).start()
|
|
|
|
|
|
|
|
with caplog.at_level(logging.INFO):
|
|
|
|
updater.idle()
|
|
|
|
|
2021-05-19 13:33:41 +02:00
|
|
|
# There is a chance of a conflict when getting updates since there can be many tests
|
|
|
|
# (bots) running simultaneously while testing in github actions.
|
2021-06-26 22:19:59 +02:00
|
|
|
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'):
|
2021-05-28 22:24:43 +02:00
|
|
|
caplog.records.pop(idx) # For stability
|
2021-06-26 22:19:59 +02:00
|
|
|
|
|
|
|
if msg.startswith('No error handlers are registered'):
|
|
|
|
caplog.records.pop(idx)
|
|
|
|
|
|
|
|
assert len(caplog.records) == 2, caplog.records
|
2021-05-19 13:33:41 +02:00
|
|
|
|
2020-07-10 13:11:28 +02:00
|
|
|
rec = caplog.records[-2]
|
2020-11-23 22:09:29 +01:00
|
|
|
assert rec.getMessage().startswith(f'Received signal {signal.SIGTERM}')
|
2018-01-09 16:54:07 +01:00
|
|
|
assert rec.levelname == 'INFO'
|
|
|
|
|
2020-07-10 13:11:28 +02:00
|
|
|
rec = caplog.records[-1]
|
2020-10-31 16:33:34 +01:00
|
|
|
assert rec.getMessage().startswith('Scheduler has been shut down')
|
2020-07-10 13:11:28 +02:00
|
|
|
assert rec.levelname == 'INFO'
|
|
|
|
|
2015-11-24 14:57:54 +01:00
|
|
|
# If we get this far, idle() ran through
|
2020-10-09 17:22:07 +02:00
|
|
|
sleep(0.5)
|
2017-08-11 23:58:41 +02:00
|
|
|
assert updater.running is False
|
2015-11-24 14:57:54 +01:00
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
@signalskip
|
|
|
|
def test_user_signal(self, updater):
|
|
|
|
temp_var = {'a': 0}
|
2017-03-26 14:36:13 +02:00
|
|
|
|
2017-08-11 23:58:41 +02:00
|
|
|
def user_signal_inc(signum, frame):
|
|
|
|
temp_var['a'] = 1
|
2017-03-26 14:36:13 +02:00
|
|
|
|
2021-10-09 13:56:50 +02:00
|
|
|
updater.user_signal_handler = user_signal_inc
|
2017-08-11 23:58:41 +02:00
|
|
|
updater.start_polling(0.01)
|
2018-01-09 16:54:07 +01:00
|
|
|
Thread(target=partial(self.signal_sender, updater=updater)).start()
|
2017-08-11 23:58:41 +02:00
|
|
|
updater.idle()
|
2017-03-26 14:36:13 +02:00
|
|
|
# If we get this far, idle() ran through
|
2020-10-09 17:22:07 +02:00
|
|
|
sleep(0.5)
|
2017-08-11 23:58:41 +02:00
|
|
|
assert updater.running is False
|
|
|
|
assert temp_var['a'] != 0
|