Added full support for the Bot API 9.6 (#1792)

* Added full support for the Bot API 9.6

* Add support for `managed_bot` updates

* Set `description_parse_mode` default to `"parse_mode"` and use `DateTime` for `addition_date` in `PollOption`

* Update changelog with features and changes from Bot API 9.6

* Add changelog fragment generator and update poll parameter descriptions
This commit is contained in:
Alex Root Junior 2026-04-04 01:22:08 +03:00 committed by GitHub
parent 00c1130938
commit 9f49c0413f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
107 changed files with 3077 additions and 328 deletions

View file

@ -0,0 +1,11 @@
from aiogram.methods import GetManagedBotToken
from tests.mocked_bot import MockedBot
class TestGetManagedBotToken:
async def test_bot_method(self, bot: MockedBot):
prepare_result = bot.add_result_for(GetManagedBotToken, ok=True, result="42:NEW_TOKEN")
response: str = await bot.get_managed_bot_token(user_id=42)
bot.get_request()
assert response == prepare_result.result

View file

@ -0,0 +1,13 @@
from aiogram.methods import ReplaceManagedBotToken
from tests.mocked_bot import MockedBot
class TestReplaceManagedBotToken:
async def test_bot_method(self, bot: MockedBot):
prepare_result = bot.add_result_for(
ReplaceManagedBotToken, ok=True, result="42:REPLACED_TOKEN"
)
response: str = await bot.replace_managed_bot_token(user_id=42)
bot.get_request()
assert response == prepare_result.result

View file

@ -0,0 +1,22 @@
from aiogram.methods import SavePreparedKeyboardButton
from aiogram.types import KeyboardButton, KeyboardButtonRequestManagedBot, PreparedKeyboardButton
from tests.mocked_bot import MockedBot
class TestSavePreparedKeyboardButton:
async def test_bot_method(self, bot: MockedBot):
prepare_result = bot.add_result_for(
SavePreparedKeyboardButton,
ok=True,
result=PreparedKeyboardButton(id="test-id"),
)
response: PreparedKeyboardButton = await bot.save_prepared_keyboard_button(
user_id=42,
button=KeyboardButton(
text="Create bot",
request_managed_bot=KeyboardButtonRequestManagedBot(request_id=1),
),
)
bot.get_request()
assert response == prepare_result.result

View file

@ -17,13 +17,14 @@ class TestSendPoll:
id="QA",
question="Q",
options=[
PollOption(text="A", voter_count=0),
PollOption(text="B", voter_count=0),
PollOption(persistent_id="1", text="A", voter_count=0),
PollOption(persistent_id="2", text="B", voter_count=0),
],
is_closed=False,
is_anonymous=False,
type="quiz",
allows_multiple_answers=False,
allows_revoting=False,
total_voter_count=0,
correct_option_id=0,
),

View file

@ -11,11 +11,15 @@ class TestStopPoll:
result=Poll(
id="QA",
question="Q",
options=[PollOption(text="A", voter_count=0), PollOption(text="B", voter_count=0)],
options=[
PollOption(persistent_id="1", text="A", voter_count=0),
PollOption(persistent_id="2", text="B", voter_count=0),
],
is_closed=False,
is_anonymous=False,
type="quiz",
allows_multiple_answers=False,
allows_revoting=False,
total_voter_count=0,
correct_option_id=0,
),

View file

@ -0,0 +1,19 @@
from aiogram.types import KeyboardButtonRequestManagedBot
class TestKeyboardButtonRequestManagedBot:
def test_required_fields(self):
obj = KeyboardButtonRequestManagedBot(request_id=1)
assert obj.request_id == 1
assert obj.suggested_name is None
assert obj.suggested_username is None
def test_optional_fields(self):
obj = KeyboardButtonRequestManagedBot(
request_id=2,
suggested_name="My Bot",
suggested_username="my_bot",
)
assert obj.request_id == 2
assert obj.suggested_name == "My Bot"
assert obj.suggested_username == "my_bot"

View file

@ -0,0 +1,9 @@
from aiogram.types import ManagedBotCreated, User
class TestManagedBotCreated:
def test_fields(self):
bot_user = User(id=123, is_bot=True, first_name="TestBot")
obj = ManagedBotCreated(bot=bot_user)
assert obj.bot_user == bot_user
assert obj.bot_user.id == 123

View file

@ -0,0 +1,10 @@
from aiogram.types import ManagedBotUpdated, User
class TestManagedBotUpdated:
def test_fields(self):
user = User(id=42, is_bot=False, first_name="Creator")
bot_user = User(id=123, is_bot=True, first_name="TestBot")
obj = ManagedBotUpdated(user=user, bot=bot_user)
assert obj.user == user
assert obj.bot_user == bot_user

View file

@ -76,6 +76,7 @@ from aiogram.types import (
InputMediaPhoto,
Invoice,
Location,
ManagedBotCreated,
MessageAutoDeleteTimerChanged,
MessageEntity,
PaidMediaInfo,
@ -85,6 +86,8 @@ from aiogram.types import (
PhotoSize,
Poll,
PollOption,
PollOptionAdded,
PollOptionDeleted,
ProximityAlertTriggered,
ReactionTypeCustomEmoji,
RefundedPayment,
@ -426,13 +429,14 @@ TEST_MESSAGE_POLL = Message(
id="QA",
question="Q",
options=[
PollOption(text="A", voter_count=0),
PollOption(text="B", voter_count=0),
PollOption(persistent_id="1", text="A", voter_count=0),
PollOption(persistent_id="2", text="B", voter_count=0),
],
is_closed=False,
is_anonymous=False,
type="quiz",
allows_multiple_answers=False,
allows_revoting=False,
total_voter_count=0,
correct_option_id=1,
),
@ -858,6 +862,35 @@ TEST_MESSAGE_SUGGESTED_POST_REFUNDED = Message(
from_user=User(id=42, is_bot=False, first_name="Test"),
suggested_post_refunded=SuggestedPostRefunded(reason="post_deleted"),
)
TEST_MESSAGE_MANAGED_BOT_CREATED = Message(
message_id=42,
date=datetime.datetime.now(),
chat=Chat(id=42, type="private"),
from_user=User(id=42, is_bot=False, first_name="Test"),
managed_bot_created=ManagedBotCreated(
bot_user=User(id=100, is_bot=True, first_name="ManagedBot"),
),
)
TEST_MESSAGE_POLL_OPTION_ADDED = Message(
message_id=42,
date=datetime.datetime.now(),
chat=Chat(id=42, type="private"),
from_user=User(id=42, is_bot=False, first_name="Test"),
poll_option_added=PollOptionAdded(
option_persistent_id="1",
option_text="New option",
),
)
TEST_MESSAGE_POLL_OPTION_DELETED = Message(
message_id=42,
date=datetime.datetime.now(),
chat=Chat(id=42, type="private"),
from_user=User(id=42, is_bot=False, first_name="Test"),
poll_option_deleted=PollOptionDeleted(
option_persistent_id="1",
option_text="Deleted option",
),
)
MESSAGES_AND_CONTENT_TYPES = [
[TEST_MESSAGE_TEXT, ContentType.TEXT],
@ -937,6 +970,9 @@ MESSAGES_AND_CONTENT_TYPES = [
[TEST_MESSAGE_SUGGESTED_POST_DECLINED, ContentType.SUGGESTED_POST_DECLINED],
[TEST_MESSAGE_SUGGESTED_POST_PAID, ContentType.SUGGESTED_POST_PAID],
[TEST_MESSAGE_SUGGESTED_POST_REFUNDED, ContentType.SUGGESTED_POST_REFUNDED],
[TEST_MESSAGE_MANAGED_BOT_CREATED, ContentType.MANAGED_BOT_CREATED],
[TEST_MESSAGE_POLL_OPTION_ADDED, ContentType.POLL_OPTION_ADDED],
[TEST_MESSAGE_POLL_OPTION_DELETED, ContentType.POLL_OPTION_DELETED],
[TEST_MESSAGE_UNKNOWN, ContentType.UNKNOWN],
]
@ -1013,6 +1049,9 @@ MESSAGES_AND_COPY_METHODS = [
[TEST_MESSAGE_SUGGESTED_POST_DECLINED, None],
[TEST_MESSAGE_SUGGESTED_POST_PAID, None],
[TEST_MESSAGE_SUGGESTED_POST_REFUNDED, None],
[TEST_MESSAGE_MANAGED_BOT_CREATED, None],
[TEST_MESSAGE_POLL_OPTION_ADDED, None],
[TEST_MESSAGE_POLL_OPTION_DELETED, None],
[TEST_MESSAGE_UNKNOWN, None],
]

View file

@ -0,0 +1,18 @@
from aiogram.types import PollOptionAdded
class TestPollOptionAdded:
def test_required_fields(self):
obj = PollOptionAdded(option_persistent_id="opt1", option_text="Option A")
assert obj.option_persistent_id == "opt1"
assert obj.option_text == "Option A"
assert obj.poll_message is None
assert obj.option_text_entities is None
def test_optional_fields(self):
obj = PollOptionAdded(
option_persistent_id="opt2",
option_text="Option B",
option_text_entities=[],
)
assert obj.option_text_entities == []

View file

@ -0,0 +1,18 @@
from aiogram.types import PollOptionDeleted
class TestPollOptionDeleted:
def test_required_fields(self):
obj = PollOptionDeleted(option_persistent_id="opt1", option_text="Option A")
assert obj.option_persistent_id == "opt1"
assert obj.option_text == "Option A"
assert obj.poll_message is None
assert obj.option_text_entities is None
def test_optional_fields(self):
obj = PollOptionDeleted(
option_persistent_id="opt2",
option_text="Option B",
option_text_entities=[],
)
assert obj.option_text_entities == []

View file

@ -0,0 +1,7 @@
from aiogram.types import PreparedKeyboardButton
class TestPreparedKeyboardButton:
def test_fields(self):
obj = PreparedKeyboardButton(id="abc123")
assert obj.id == "abc123"

View file

@ -30,6 +30,7 @@ from aiogram.types import (
ChatMemberUpdated,
ChosenInlineResult,
InlineQuery,
ManagedBotUpdated,
Message,
MessageReactionCountUpdated,
MessageReactionUpdated,
@ -380,13 +381,14 @@ class TestDispatcher:
id="poll id",
question="Q?",
options=[
PollOption(text="A1", voter_count=2),
PollOption(text="A2", voter_count=3),
PollOption(persistent_id="1", text="A1", voter_count=2),
PollOption(persistent_id="2", text="A2", voter_count=3),
],
is_closed=False,
is_anonymous=False,
type="quiz",
allows_multiple_answers=False,
allows_revoting=False,
total_voter_count=0,
correct_option_id=1,
),
@ -402,6 +404,7 @@ class TestDispatcher:
poll_id="poll id",
user=User(id=42, is_bot=False, first_name="Test"),
option_ids=[42],
option_persistent_ids=["1"],
),
),
False,
@ -600,6 +603,18 @@ class TestDispatcher:
False,
True,
),
pytest.param(
"managed_bot",
Update(
update_id=42,
managed_bot=ManagedBotUpdated(
user=User(id=42, is_bot=False, first_name="Test"),
bot_user=User(id=100, is_bot=True, first_name="ManagedBot"),
),
),
False,
True,
),
],
)
async def test_listen_update(
@ -655,13 +670,14 @@ class TestDispatcher:
id="poll id",
question="Q?",
options=[
PollOption(text="A1", voter_count=2),
PollOption(text="A2", voter_count=3),
PollOption(persistent_id="1", text="A1", voter_count=2),
PollOption(persistent_id="2", text="A2", voter_count=3),
],
is_closed=False,
is_anonymous=False,
type="quiz",
allows_multiple_answers=False,
allows_revoting=False,
total_voter_count=0,
correct_option_id=0,
),

View file

@ -71,6 +71,7 @@ class TestRouter:
assert router.observers["shipping_query"] == router.shipping_query
assert router.observers["pre_checkout_query"] == router.pre_checkout_query
assert router.observers["poll"] == router.poll
assert router.observers["managed_bot"] == router.managed_bot
async def test_emit_startup(self):
router1 = Router()

View file

@ -9,11 +9,12 @@ class TestShippingQueryHandler:
event = Poll(
id="query",
question="Q?",
options=[PollOption(text="A1", voter_count=1)],
options=[PollOption(persistent_id="1", text="A1", voter_count=1)],
is_closed=True,
is_anonymous=False,
type="quiz",
allows_multiple_answers=False,
allows_revoting=False,
total_voter_count=0,
correct_option_id=0,
)