From f578cafeca92d4ca8e58939518a6b7bd19c8c84b Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Mon, 29 Sep 2025 18:18:42 +0000 Subject: [PATCH 01/11] Add support for Telegram message attachments and corresponding event handling --- homeassistant/components/telegram_bot/bot.py | 17 +++++++ .../components/telegram_bot/const.py | 2 + tests/components/telegram_bot/conftest.py | 49 +++++++++++++++++++ .../telegram_bot/test_telegram_bot.py | 30 ++++++++++++ 4 files changed, 98 insertions(+) diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index 42bd493489b73e..01ac44e19f19c6 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -50,6 +50,7 @@ ATTR_DISABLE_NOTIF, ATTR_DISABLE_WEB_PREV, ATTR_FILE, + ATTR_FILE_ID, ATTR_FROM_FIRST, ATTR_FROM_LAST, ATTR_KEYBOARD, @@ -78,6 +79,7 @@ CONF_CHAT_ID, CONF_PROXY_URL, DOMAIN, + EVENT_TELEGRAM_ATTACHMENT, EVENT_TELEGRAM_CALLBACK, EVENT_TELEGRAM_COMMAND, EVENT_TELEGRAM_SENT, @@ -175,6 +177,10 @@ def _get_message_event_data(self, message: Message) -> tuple[str, dict[str, Any] # This is a command message - set event type to command and split data into command and args event_type = EVENT_TELEGRAM_COMMAND event_data.update(self._get_command_event_data(message.text)) + elif filters.ATTACHMENT.filter(message): + event_type = EVENT_TELEGRAM_ATTACHMENT + event_data[ATTR_TEXT] = message.caption + event_data.update(self._get_file_id_event_data(message)) else: event_type = EVENT_TELEGRAM_TEXT event_data[ATTR_TEXT] = message.text @@ -184,6 +190,17 @@ def _get_message_event_data(self, message: Message) -> tuple[str, dict[str, Any] return event_type, event_data + def _get_file_id_event_data(self, message: Message) -> dict[str, Any]: + """Extract file_id from a message attachment, if any.""" + event_data: dict[str, Any] = {} + file_id = None + if filters.PHOTO.filter(message): + file_id = message.effective_attachment[-1].file_id # type: ignore[index,union-attr] + elif hasattr(message.effective_attachment, "file_id"): + file_id = message.effective_attachment.file_id # type: ignore[union-attr] + event_data[ATTR_FILE_ID] = file_id + return event_data + def _get_user_event_data(self, user: User) -> dict[str, Any]: return { ATTR_USER_ID: user.id, diff --git a/homeassistant/components/telegram_bot/const.py b/homeassistant/components/telegram_bot/const.py index 34b8a476c788cf..849b6eb3b60421 100644 --- a/homeassistant/components/telegram_bot/const.py +++ b/homeassistant/components/telegram_bot/const.py @@ -53,6 +53,7 @@ EVENT_TELEGRAM_CALLBACK = "telegram_callback" EVENT_TELEGRAM_COMMAND = "telegram_command" EVENT_TELEGRAM_TEXT = "telegram_text" +EVENT_TELEGRAM_ATTACHMENT = "telegram_attachment" EVENT_TELEGRAM_SENT = "telegram_sent" PARSER_HTML = "html" @@ -89,6 +90,7 @@ ATTR_DISABLE_WEB_PREV = "disable_web_page_preview" ATTR_EDITED_MSG = "edited_message" ATTR_FILE = "file" +ATTR_FILE_ID = "file_id" ATTR_FROM_FIRST = "from_first" ATTR_FROM_LAST = "from_last" ATTR_KEYBOARD = "keyboard" diff --git a/tests/components/telegram_bot/conftest.py b/tests/components/telegram_bot/conftest.py index 489cb034ac23d3..58ddf699b4a206 100644 --- a/tests/components/telegram_bot/conftest.py +++ b/tests/components/telegram_bot/conftest.py @@ -242,6 +242,55 @@ def update_callback_query(): } +@pytest.fixture +def update_message_attachment(): + """Fixture for mocking an incoming update of type message/attachment.""" + return { + "update_id": 1, + "message": { + "message_id": 1, + "date": 1441645532, + "from": { + "id": 12345678, + "is_bot": False, + "last_name": "Test Lastname", + "first_name": "Test Firstname", + "username": "Testusername", + }, + "chat": { + "last_name": "Test Lastname", + "id": 1111111, + "type": "private", + "first_name": "Test Firstname", + "username": "Testusername", + }, + "photo": [ + { + "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", + "file_unique_id": "AQADcbzEbT-5xuBa", + "file_size": 1234, + "width": 90, + "height": 90, + }, + { + "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", + "file_unique_id": "AQADcbzEbT-5xuBa", + "file_size": 12345, + "width": 320, + "height": 320, + }, + { + "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", + "file_unique_id": "AQADcbzEbT-5xuBa", + "file_size": 123456, + "width": 800, + "height": 800, + }, + ], + }, + } + + @pytest.fixture def mock_broadcast_config_entry() -> MockConfigEntry: """Return the default mocked config entry.""" diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index cda2583e74bb84..1a0fa2b9405117 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -476,6 +476,36 @@ async def test_webhook_endpoint_generates_telegram_callback_event( assert isinstance(events[0].context, Context) +async def test_webhook_endpoint_generates_telegram_attachment_event( + hass: HomeAssistant, + webhook_platform, + hass_client: ClientSessionGenerator, + update_message_attachment, + mock_generate_secret_token, +) -> None: + """POST to the configured webhook endpoint and assert fired `telegram_attachment` event.""" + client = await hass_client() + events = async_capture_events(hass, "telegram_attachment") + + response = await client.post( + f"{TELEGRAM_WEBHOOK_URL}_123456", + json=update_message_attachment, + headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token}, + ) + assert response.status == 200 + assert (await response.read()).decode("utf-8") == "" + + # Make sure event has fired + await hass.async_block_till_done() + + assert len(events) == 1 + assert ( + events[0].data["file_id"] + == update_message_attachment["message"]["photo"][-1]["file_id"] + ) + assert isinstance(events[0].context, Context) + + async def test_polling_platform_message_text_update( hass: HomeAssistant, config_polling, From 485962ede8b77e0dc8a68b43678be5b88b521c0c Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Tue, 30 Sep 2025 07:12:41 +0000 Subject: [PATCH 02/11] Add also file mime type field --- homeassistant/components/telegram_bot/bot.py | 5 +++++ homeassistant/components/telegram_bot/const.py | 1 + 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index 01ac44e19f19c6..b27ac49989079a 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -51,6 +51,7 @@ ATTR_DISABLE_WEB_PREV, ATTR_FILE, ATTR_FILE_ID, + ATTR_FILE_MIME_TYPE, ATTR_FROM_FIRST, ATTR_FROM_LAST, ATTR_KEYBOARD, @@ -194,11 +195,15 @@ def _get_file_id_event_data(self, message: Message) -> dict[str, Any]: """Extract file_id from a message attachment, if any.""" event_data: dict[str, Any] = {} file_id = None + mime_type = None if filters.PHOTO.filter(message): file_id = message.effective_attachment[-1].file_id # type: ignore[index,union-attr] + mime_type = "image/jpeg" # telegram always uses jpeg for photos elif hasattr(message.effective_attachment, "file_id"): file_id = message.effective_attachment.file_id # type: ignore[union-attr] + mime_type = message.effective_attachment.mime_type # type: ignore[union-attr] event_data[ATTR_FILE_ID] = file_id + event_data[ATTR_FILE_MIME_TYPE] = mime_type return event_data def _get_user_event_data(self, user: User) -> dict[str, Any]: diff --git a/homeassistant/components/telegram_bot/const.py b/homeassistant/components/telegram_bot/const.py index 849b6eb3b60421..23ef5b7418ee09 100644 --- a/homeassistant/components/telegram_bot/const.py +++ b/homeassistant/components/telegram_bot/const.py @@ -91,6 +91,7 @@ ATTR_EDITED_MSG = "edited_message" ATTR_FILE = "file" ATTR_FILE_ID = "file_id" +ATTR_FILE_MIME_TYPE = "file_mime_type" ATTR_FROM_FIRST = "from_first" ATTR_FROM_LAST = "from_last" ATTR_KEYBOARD = "keyboard" From 925b75835c7b8f48ba485ebfbdd5dc0f9137c24a Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Tue, 30 Sep 2025 13:59:10 +0000 Subject: [PATCH 03/11] Add file name attribute to Telegram bot event data --- homeassistant/components/telegram_bot/bot.py | 4 ++++ homeassistant/components/telegram_bot/const.py | 1 + 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index b27ac49989079a..78481e2dadcbbc 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -52,6 +52,7 @@ ATTR_FILE, ATTR_FILE_ID, ATTR_FILE_MIME_TYPE, + ATTR_FILE_NAME, ATTR_FROM_FIRST, ATTR_FROM_LAST, ATTR_KEYBOARD, @@ -196,14 +197,17 @@ def _get_file_id_event_data(self, message: Message) -> dict[str, Any]: event_data: dict[str, Any] = {} file_id = None mime_type = None + file_name = None if filters.PHOTO.filter(message): file_id = message.effective_attachment[-1].file_id # type: ignore[index,union-attr] mime_type = "image/jpeg" # telegram always uses jpeg for photos elif hasattr(message.effective_attachment, "file_id"): file_id = message.effective_attachment.file_id # type: ignore[union-attr] mime_type = message.effective_attachment.mime_type # type: ignore[union-attr] + file_name = message.effective_attachment.file_name # type: ignore[union-attr] event_data[ATTR_FILE_ID] = file_id event_data[ATTR_FILE_MIME_TYPE] = mime_type + event_data[ATTR_FILE_NAME] = file_name return event_data def _get_user_event_data(self, user: User) -> dict[str, Any]: diff --git a/homeassistant/components/telegram_bot/const.py b/homeassistant/components/telegram_bot/const.py index 23ef5b7418ee09..f55498553f4cae 100644 --- a/homeassistant/components/telegram_bot/const.py +++ b/homeassistant/components/telegram_bot/const.py @@ -92,6 +92,7 @@ ATTR_FILE = "file" ATTR_FILE_ID = "file_id" ATTR_FILE_MIME_TYPE = "file_mime_type" +ATTR_FILE_NAME = "file_name" ATTR_FROM_FIRST = "from_first" ATTR_FROM_LAST = "from_last" ATTR_KEYBOARD = "keyboard" From 4d177956b727406167acba71df70471d02d4c573 Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Tue, 30 Sep 2025 14:04:02 +0000 Subject: [PATCH 04/11] small refactor to make it more readable --- homeassistant/components/telegram_bot/bot.py | 25 +++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index 78481e2dadcbbc..8d9e24d14aeaf6 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -194,21 +194,18 @@ def _get_message_event_data(self, message: Message) -> tuple[str, dict[str, Any] def _get_file_id_event_data(self, message: Message) -> dict[str, Any]: """Extract file_id from a message attachment, if any.""" - event_data: dict[str, Any] = {} - file_id = None - mime_type = None - file_name = None if filters.PHOTO.filter(message): - file_id = message.effective_attachment[-1].file_id # type: ignore[index,union-attr] - mime_type = "image/jpeg" # telegram always uses jpeg for photos - elif hasattr(message.effective_attachment, "file_id"): - file_id = message.effective_attachment.file_id # type: ignore[union-attr] - mime_type = message.effective_attachment.mime_type # type: ignore[union-attr] - file_name = message.effective_attachment.file_name # type: ignore[union-attr] - event_data[ATTR_FILE_ID] = file_id - event_data[ATTR_FILE_MIME_TYPE] = mime_type - event_data[ATTR_FILE_NAME] = file_name - return event_data + return { + ATTR_FILE_ID: message.effective_attachment[-1].file_id, # type: ignore[index,union-attr] + ATTR_FILE_MIME_TYPE: "image/jpeg", # telegram always uses jpeg for photos + } + if hasattr(message.effective_attachment, "file_id"): + return { + ATTR_FILE_ID: message.effective_attachment.file_id, # type: ignore[union-attr] + ATTR_FILE_MIME_TYPE: message.effective_attachment.mime_type, # type: ignore[union-attr] + ATTR_FILE_NAME: message.effective_attachment.file_name, # type: ignore[union-attr] + } + return {} def _get_user_event_data(self, user: User) -> dict[str, Any]: return { From adc3ddad953148d3ab9e6baef58e1b0123c3ef26 Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Tue, 30 Sep 2025 18:39:45 +0000 Subject: [PATCH 05/11] solving mypy issues and cleaner implementation --- homeassistant/components/telegram_bot/bot.py | 22 ++++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index 8d9e24d14aeaf6..c8e7b9d1d8a409 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -7,7 +7,7 @@ import logging from ssl import SSLContext from types import MappingProxyType -from typing import Any +from typing import Any, cast import httpx from telegram import ( @@ -17,6 +17,7 @@ InlineKeyboardMarkup, InputPollOption, Message, + PhotoSize, ReplyKeyboardMarkup, ReplyKeyboardRemove, Update, @@ -195,17 +196,20 @@ def _get_message_event_data(self, message: Message) -> tuple[str, dict[str, Any] def _get_file_id_event_data(self, message: Message) -> dict[str, Any]: """Extract file_id from a message attachment, if any.""" if filters.PHOTO.filter(message): + photos = cast(Sequence[PhotoSize], message.effective_attachment) return { - ATTR_FILE_ID: message.effective_attachment[-1].file_id, # type: ignore[index,union-attr] + ATTR_FILE_ID: photos[-1].file_id, ATTR_FILE_MIME_TYPE: "image/jpeg", # telegram always uses jpeg for photos } - if hasattr(message.effective_attachment, "file_id"): - return { - ATTR_FILE_ID: message.effective_attachment.file_id, # type: ignore[union-attr] - ATTR_FILE_MIME_TYPE: message.effective_attachment.mime_type, # type: ignore[union-attr] - ATTR_FILE_NAME: message.effective_attachment.file_name, # type: ignore[union-attr] - } - return {} + return { + k: getattr(message.effective_attachment, v) + for k, v in ( + (ATTR_FILE_ID, "file_id"), + (ATTR_FILE_NAME, "file_name"), + (ATTR_FILE_MIME_TYPE, "mime_type"), + ) + if hasattr(message.effective_attachment, v) + } def _get_user_event_data(self, user: User) -> dict[str, Any]: return { From 4e555419ef894e53c03ba1ff593b6328b25b1d52 Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Wed, 8 Oct 2025 14:49:56 +0000 Subject: [PATCH 06/11] Add file size attribute to Telegram bot event data --- homeassistant/components/telegram_bot/bot.py | 3 +++ homeassistant/components/telegram_bot/const.py | 1 + 2 files changed, 4 insertions(+) diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index c8e7b9d1d8a409..bba5e4bb64982b 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -54,6 +54,7 @@ ATTR_FILE_ID, ATTR_FILE_MIME_TYPE, ATTR_FILE_NAME, + ATTR_FILE_SIZE, ATTR_FROM_FIRST, ATTR_FROM_LAST, ATTR_KEYBOARD, @@ -200,6 +201,7 @@ def _get_file_id_event_data(self, message: Message) -> dict[str, Any]: return { ATTR_FILE_ID: photos[-1].file_id, ATTR_FILE_MIME_TYPE: "image/jpeg", # telegram always uses jpeg for photos + ATTR_FILE_SIZE: photos[-1].file_size, } return { k: getattr(message.effective_attachment, v) @@ -207,6 +209,7 @@ def _get_file_id_event_data(self, message: Message) -> dict[str, Any]: (ATTR_FILE_ID, "file_id"), (ATTR_FILE_NAME, "file_name"), (ATTR_FILE_MIME_TYPE, "mime_type"), + (ATTR_FILE_SIZE, "file_size"), ) if hasattr(message.effective_attachment, v) } diff --git a/homeassistant/components/telegram_bot/const.py b/homeassistant/components/telegram_bot/const.py index f55498553f4cae..4b4ada46251cf8 100644 --- a/homeassistant/components/telegram_bot/const.py +++ b/homeassistant/components/telegram_bot/const.py @@ -93,6 +93,7 @@ ATTR_FILE_ID = "file_id" ATTR_FILE_MIME_TYPE = "file_mime_type" ATTR_FILE_NAME = "file_name" +ATTR_FILE_SIZE = "file_size" ATTR_FROM_FIRST = "from_first" ATTR_FROM_LAST = "from_last" ATTR_KEYBOARD = "keyboard" From 0cc0c6bd1ad0053da35537bd8f90017051b7f6f7 Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Wed, 8 Oct 2025 15:38:26 +0000 Subject: [PATCH 07/11] Refactor telegram_bot tests: remove outdated fixture and load update message from JSON --- tests/components/telegram_bot/conftest.py | 49 ------------------- .../fixtures/update_message_attachment.json | 44 +++++++++++++++++ .../telegram_bot/test_telegram_bot.py | 18 +++++-- 3 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 tests/components/telegram_bot/fixtures/update_message_attachment.json diff --git a/tests/components/telegram_bot/conftest.py b/tests/components/telegram_bot/conftest.py index 58ddf699b4a206..489cb034ac23d3 100644 --- a/tests/components/telegram_bot/conftest.py +++ b/tests/components/telegram_bot/conftest.py @@ -242,55 +242,6 @@ def update_callback_query(): } -@pytest.fixture -def update_message_attachment(): - """Fixture for mocking an incoming update of type message/attachment.""" - return { - "update_id": 1, - "message": { - "message_id": 1, - "date": 1441645532, - "from": { - "id": 12345678, - "is_bot": False, - "last_name": "Test Lastname", - "first_name": "Test Firstname", - "username": "Testusername", - }, - "chat": { - "last_name": "Test Lastname", - "id": 1111111, - "type": "private", - "first_name": "Test Firstname", - "username": "Testusername", - }, - "photo": [ - { - "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", - "file_unique_id": "AQADcbzEbT-5xuBa", - "file_size": 1234, - "width": 90, - "height": 90, - }, - { - "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", - "file_unique_id": "AQADcbzEbT-5xuBa", - "file_size": 12345, - "width": 320, - "height": 320, - }, - { - "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", - "file_unique_id": "AQADcbzEbT-5xuBa", - "file_size": 123456, - "width": 800, - "height": 800, - }, - ], - }, - } - - @pytest.fixture def mock_broadcast_config_entry() -> MockConfigEntry: """Return the default mocked config entry.""" diff --git a/tests/components/telegram_bot/fixtures/update_message_attachment.json b/tests/components/telegram_bot/fixtures/update_message_attachment.json new file mode 100644 index 00000000000000..9610b8705983e2 --- /dev/null +++ b/tests/components/telegram_bot/fixtures/update_message_attachment.json @@ -0,0 +1,44 @@ +{ + "update_id": 1, + "message": { + "message_id": 1, + "date": 1441645532, + "from": { + "id": 12345678, + "is_bot": false, + "last_name": "Test Lastname", + "first_name": "Test Firstname", + "username": "Testusername" + }, + "chat": { + "last_name": "Test Lastname", + "id": 1111111, + "type": "private", + "first_name": "Test Firstname", + "username": "Testusername" + }, + "photo": [ + { + "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", + "file_unique_id": "AQADcbzEbT-5xuBa", + "file_size": 1234, + "width": 90, + "height": 90 + }, + { + "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", + "file_unique_id": "AQADcbzEbT-5xuBa", + "file_size": 12345, + "width": 320, + "height": 320 + }, + { + "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", + "file_unique_id": "AQADcbzEbT-5xuBa", + "file_size": 123456, + "width": 800, + "height": 800 + } + ] + } +} diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index 1a0fa2b9405117..a9c150864113c8 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -89,9 +89,10 @@ ServiceValidationError, ) from homeassistant.setup import async_setup_component +from homeassistant.util import json as json_util from homeassistant.util.file import write_utf8_file -from tests.common import MockConfigEntry, async_capture_events +from tests.common import MockConfigEntry, async_capture_events, async_load_fixture from tests.typing import ClientSessionGenerator @@ -480,17 +481,22 @@ async def test_webhook_endpoint_generates_telegram_attachment_event( hass: HomeAssistant, webhook_platform, hass_client: ClientSessionGenerator, - update_message_attachment, mock_generate_secret_token, ) -> None: """POST to the configured webhook endpoint and assert fired `telegram_attachment` event.""" client = await hass_client() events = async_capture_events(hass, "telegram_attachment") + update_message_attachment = await async_load_fixture( + hass, "update_message_attachment.json", DOMAIN + ) response = await client.post( f"{TELEGRAM_WEBHOOK_URL}_123456", - json=update_message_attachment, - headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token}, + data=update_message_attachment, + headers={ + "X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token, + "Content-Type": "application/json", + }, ) assert response.status == 200 assert (await response.read()).decode("utf-8") == "" @@ -501,7 +507,9 @@ async def test_webhook_endpoint_generates_telegram_attachment_event( assert len(events) == 1 assert ( events[0].data["file_id"] - == update_message_attachment["message"]["photo"][-1]["file_id"] + == json_util.json_loads(update_message_attachment)["message"]["photo"][-1][ + "file_id" + ] ) assert isinstance(events[0].context, Context) From c656765e5bb8a37f159b1e6bc94ed1d2b8fe4319 Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Wed, 8 Oct 2025 15:51:02 +0000 Subject: [PATCH 08/11] Add tests for Telegram bot document attachment event --- .../update_message_attachment_document.json | 27 ++++++++++++ ...n => update_message_attachment_photo.json} | 0 .../telegram_bot/test_telegram_bot.py | 43 +++++++++++++++++-- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tests/components/telegram_bot/fixtures/update_message_attachment_document.json rename tests/components/telegram_bot/fixtures/{update_message_attachment.json => update_message_attachment_photo.json} (100%) diff --git a/tests/components/telegram_bot/fixtures/update_message_attachment_document.json b/tests/components/telegram_bot/fixtures/update_message_attachment_document.json new file mode 100644 index 00000000000000..4d6fde54196b87 --- /dev/null +++ b/tests/components/telegram_bot/fixtures/update_message_attachment_document.json @@ -0,0 +1,27 @@ +{ + "update_id": 2, + "message": { + "message_id": 2, + "date": 1441645532, + "from": { + "id": 12345678, + "is_bot": false, + "last_name": "Test Lastname", + "first_name": "Test Firstname", + "username": "Testusername" + }, + "chat": { + "last_name": "Test Lastname", + "id": 1111111, + "type": "private", + "first_name": "Test Firstname", + "username": "Testusername" + }, + "document": { + "file_id": "AgACAgUAAxkBAAIBUWJ5aXl6Y3h5bXl6Y3h5bXl6Y3gAAJxvMRtP7nG4Fq7m0m0vBAAMCAAN5AAMjBA", + "file_unique_id": "AQADcbzEbT-5xuBa", + "mime_type": "application/pdf", + "file_size": 123456 + } + } +} diff --git a/tests/components/telegram_bot/fixtures/update_message_attachment.json b/tests/components/telegram_bot/fixtures/update_message_attachment_photo.json similarity index 100% rename from tests/components/telegram_bot/fixtures/update_message_attachment.json rename to tests/components/telegram_bot/fixtures/update_message_attachment_photo.json diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index a9c150864113c8..5c2be9369637f8 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -477,17 +477,17 @@ async def test_webhook_endpoint_generates_telegram_callback_event( assert isinstance(events[0].context, Context) -async def test_webhook_endpoint_generates_telegram_attachment_event( +async def test_webhook_endpoint_generates_telegram_attachment_photo_event( hass: HomeAssistant, webhook_platform, hass_client: ClientSessionGenerator, mock_generate_secret_token, ) -> None: - """POST to the configured webhook endpoint and assert fired `telegram_attachment` event.""" + """POST to the configured webhook endpoint and assert fired `telegram_attachment` for photo event.""" client = await hass_client() events = async_capture_events(hass, "telegram_attachment") update_message_attachment = await async_load_fixture( - hass, "update_message_attachment.json", DOMAIN + hass, "update_message_attachment_photo.json", DOMAIN ) response = await client.post( @@ -514,6 +514,43 @@ async def test_webhook_endpoint_generates_telegram_attachment_event( assert isinstance(events[0].context, Context) +async def test_webhook_endpoint_generates_telegram_attachment_document_event( + hass: HomeAssistant, + webhook_platform, + hass_client: ClientSessionGenerator, + mock_generate_secret_token, +) -> None: + """POST to the configured webhook endpoint and assert fired `telegram_attachment` for document event.""" + client = await hass_client() + events = async_capture_events(hass, "telegram_attachment") + update_message_attachment = await async_load_fixture( + hass, "update_message_attachment_document.json", DOMAIN + ) + + response = await client.post( + f"{TELEGRAM_WEBHOOK_URL}_123456", + data=update_message_attachment, + headers={ + "X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token, + "Content-Type": "application/json", + }, + ) + assert response.status == 200 + assert (await response.read()).decode("utf-8") == "" + + # Make sure event has fired + await hass.async_block_till_done() + + assert len(events) == 1 + assert ( + events[0].data["file_id"] + == json_util.json_loads(update_message_attachment)["message"]["document"][ + "file_id" + ] + ) + assert isinstance(events[0].context, Context) + + async def test_polling_platform_message_text_update( hass: HomeAssistant, config_polling, From 39d36b333788d620c023f45b3f280afc6bc98183 Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Thu, 9 Oct 2025 12:12:03 +0000 Subject: [PATCH 09/11] Transform to parameterized test for Telegram bot attachment events --- .../telegram_bot/test_telegram_bot.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index 5c2be9369637f8..9c456b63adc0ef 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -551,6 +551,52 @@ async def test_webhook_endpoint_generates_telegram_attachment_document_event( assert isinstance(events[0].context, Context) +@pytest.mark.parametrize( + ("attachment_type"), + [ + ("photo"), + ("document"), + ], +) +async def test_webhook_endpoint_generates_telegram_attachment_event( + hass: HomeAssistant, + webhook_platform, + hass_client: ClientSessionGenerator, + mock_generate_secret_token, + attachment_type: str, +) -> None: + """POST to the configured webhook endpoint and assert fired `telegram_attachment` event for photo and document.""" + client = await hass_client() + events = async_capture_events(hass, "telegram_attachment") + update_message_attachment = await async_load_fixture( + hass, f"update_message_attachment_{attachment_type}.json", DOMAIN + ) + + response = await client.post( + f"{TELEGRAM_WEBHOOK_URL}_123456", + data=update_message_attachment, + headers={ + "X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token, + "Content-Type": "application/json", + }, + ) + assert response.status == 200 + assert (await response.read()).decode("utf-8") == "" + + # Make sure event has fired + await hass.async_block_till_done() + + assert len(events) == 1 + loaded = json_util.json_loads(update_message_attachment) + if attachment_type == "photo": + expected_file_id = loaded["message"]["photo"][-1]["file_id"] + else: + expected_file_id = loaded["message"][attachment_type]["file_id"] + + assert events[0].data["file_id"] == expected_file_id + assert isinstance(events[0].context, Context) + + async def test_polling_platform_message_text_update( hass: HomeAssistant, config_polling, From e89c63d9db52405b5aa6c548708f9d70fd4781c0 Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Thu, 9 Oct 2025 20:17:12 +0000 Subject: [PATCH 10/11] remove leftovers --- .../telegram_bot/test_telegram_bot.py | 76 +------------------ 1 file changed, 1 insertion(+), 75 deletions(-) diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index 9c456b63adc0ef..9bfeb3df547a5d 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -477,80 +477,6 @@ async def test_webhook_endpoint_generates_telegram_callback_event( assert isinstance(events[0].context, Context) -async def test_webhook_endpoint_generates_telegram_attachment_photo_event( - hass: HomeAssistant, - webhook_platform, - hass_client: ClientSessionGenerator, - mock_generate_secret_token, -) -> None: - """POST to the configured webhook endpoint and assert fired `telegram_attachment` for photo event.""" - client = await hass_client() - events = async_capture_events(hass, "telegram_attachment") - update_message_attachment = await async_load_fixture( - hass, "update_message_attachment_photo.json", DOMAIN - ) - - response = await client.post( - f"{TELEGRAM_WEBHOOK_URL}_123456", - data=update_message_attachment, - headers={ - "X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token, - "Content-Type": "application/json", - }, - ) - assert response.status == 200 - assert (await response.read()).decode("utf-8") == "" - - # Make sure event has fired - await hass.async_block_till_done() - - assert len(events) == 1 - assert ( - events[0].data["file_id"] - == json_util.json_loads(update_message_attachment)["message"]["photo"][-1][ - "file_id" - ] - ) - assert isinstance(events[0].context, Context) - - -async def test_webhook_endpoint_generates_telegram_attachment_document_event( - hass: HomeAssistant, - webhook_platform, - hass_client: ClientSessionGenerator, - mock_generate_secret_token, -) -> None: - """POST to the configured webhook endpoint and assert fired `telegram_attachment` for document event.""" - client = await hass_client() - events = async_capture_events(hass, "telegram_attachment") - update_message_attachment = await async_load_fixture( - hass, "update_message_attachment_document.json", DOMAIN - ) - - response = await client.post( - f"{TELEGRAM_WEBHOOK_URL}_123456", - data=update_message_attachment, - headers={ - "X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token, - "Content-Type": "application/json", - }, - ) - assert response.status == 200 - assert (await response.read()).decode("utf-8") == "" - - # Make sure event has fired - await hass.async_block_till_done() - - assert len(events) == 1 - assert ( - events[0].data["file_id"] - == json_util.json_loads(update_message_attachment)["message"]["document"][ - "file_id" - ] - ) - assert isinstance(events[0].context, Context) - - @pytest.mark.parametrize( ("attachment_type"), [ @@ -562,7 +488,7 @@ async def test_webhook_endpoint_generates_telegram_attachment_event( hass: HomeAssistant, webhook_platform, hass_client: ClientSessionGenerator, - mock_generate_secret_token, + mock_generate_secret_token: str, attachment_type: str, ) -> None: """POST to the configured webhook endpoint and assert fired `telegram_attachment` event for photo and document.""" From 527aa157d82eae7c626b77f598f3208e6b16f0b0 Mon Sep 17 00:00:00 2001 From: aviadlevy Date: Thu, 9 Oct 2025 20:21:16 +0000 Subject: [PATCH 11/11] add typing --- tests/components/telegram_bot/test_telegram_bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index 9bfeb3df547a5d..dd5d01aad8d20f 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -486,7 +486,7 @@ async def test_webhook_endpoint_generates_telegram_callback_event( ) async def test_webhook_endpoint_generates_telegram_attachment_event( hass: HomeAssistant, - webhook_platform, + webhook_platform: None, hass_client: ClientSessionGenerator, mock_generate_secret_token: str, attachment_type: str,