@@ -156,7 +156,7 @@ async def test_telegram_reply_without_quote_text_uses_full_message(quote_text):
156156
157157
158158@pytest .mark .asyncio
159- async def test_telegram_document_caption_populates_message_text_and_plain ():
159+ async def test_telegram_document_caption_populates_message_text_and_plain (tmp_path ):
160160 TelegramPlatformAdapter = _load_telegram_adapter ()
161161 adapter = TelegramPlatformAdapter (
162162 make_platform_config ("telegram" ),
@@ -172,8 +172,16 @@ async def test_telegram_document_caption_populates_message_text_and_plain():
172172 caption = "@alice 请总结这份文档" ,
173173 caption_entities = [mention ],
174174 )
175+ convert_message_globals = adapter .convert_message .__func__ .__globals__
175176
176- result = await adapter .convert_message (update , _build_context ())
177+ with patch .dict (
178+ convert_message_globals ,
179+ {
180+ "get_astrbot_temp_path" : MagicMock (return_value = str (tmp_path )),
181+ "download_file" : AsyncMock (),
182+ },
183+ ):
184+ result = await adapter .convert_message (update , _build_context ())
177185
178186 assert result is not None
179187 assert result .message_str == "@alice 请总结这份文档"
@@ -189,7 +197,7 @@ async def test_telegram_document_caption_populates_message_text_and_plain():
189197
190198
191199@pytest .mark .asyncio
192- async def test_telegram_video_caption_populates_message_text_and_plain ():
200+ async def test_telegram_video_caption_populates_message_text_and_plain (tmp_path ):
193201 TelegramPlatformAdapter = _load_telegram_adapter ()
194202 adapter = TelegramPlatformAdapter (
195203 make_platform_config ("telegram" ),
@@ -203,8 +211,16 @@ async def test_telegram_video_caption_populates_message_text_and_plain():
203211 video = video ,
204212 caption = "这段视频讲了什么" ,
205213 )
214+ convert_message_globals = adapter .convert_message .__func__ .__globals__
206215
207- result = await adapter .convert_message (update , _build_context ())
216+ with patch .dict (
217+ convert_message_globals ,
218+ {
219+ "get_astrbot_temp_path" : MagicMock (return_value = str (tmp_path )),
220+ "download_file" : AsyncMock (),
221+ },
222+ ):
223+ result = await adapter .convert_message (update , _build_context ())
208224
209225 assert result is not None
210226 assert result .message_str == "这段视频讲了什么"
@@ -215,6 +231,133 @@ async def test_telegram_video_caption_populates_message_text_and_plain():
215231 )
216232
217233
234+ @pytest .mark .asyncio
235+ async def test_telegram_document_downloads_to_local_temp_path (tmp_path ):
236+ """#9448: document 组件必须拿到本地路径,而不是原始 Telegram file_path/URL。"""
237+ TelegramPlatformAdapter = _load_telegram_adapter ()
238+ adapter = TelegramPlatformAdapter (
239+ make_platform_config ("telegram" ),
240+ {},
241+ asyncio .Queue (),
242+ )
243+ document = create_mock_file ("https://api.telegram.org/file/test/report.md" )
244+ document .file_name = "report.md"
245+ update = create_mock_update (message_text = None , document = document )
246+ convert_message_globals = adapter .convert_message .__func__ .__globals__
247+ mock_download = AsyncMock ()
248+
249+ with patch .dict (
250+ convert_message_globals ,
251+ {
252+ "get_astrbot_temp_path" : MagicMock (return_value = str (tmp_path )),
253+ "download_file" : mock_download ,
254+ },
255+ ):
256+ result = await adapter .convert_message (update , _build_context ())
257+
258+ assert result is not None
259+ file_comp = next (c for c in result .message if isinstance (c , Comp .File ))
260+ assert file_comp .name == "report.md"
261+ assert file_comp .file_ .startswith (str (tmp_path ))
262+ assert file_comp .file_ != "https://api.telegram.org/file/test/report.md"
263+ mock_download .assert_awaited_once ()
264+ assert (
265+ mock_download .await_args .args [0 ]
266+ == "https://api.telegram.org/file/test/report.md"
267+ )
268+
269+
270+ @pytest .mark .asyncio
271+ async def test_telegram_video_downloads_to_local_temp_path (tmp_path ):
272+ """#9448: video 组件必须拿到本地路径,而不是原始 Telegram file_path/URL。"""
273+ TelegramPlatformAdapter = _load_telegram_adapter ()
274+ adapter = TelegramPlatformAdapter (
275+ make_platform_config ("telegram" ),
276+ {},
277+ asyncio .Queue (),
278+ )
279+ video = create_mock_file ("https://api.telegram.org/file/test/lesson.mp4" )
280+ video .file_name = "lesson.mp4"
281+ update = create_mock_update (message_text = None , video = video )
282+ convert_message_globals = adapter .convert_message .__func__ .__globals__
283+ mock_download = AsyncMock ()
284+
285+ with patch .dict (
286+ convert_message_globals ,
287+ {
288+ "get_astrbot_temp_path" : MagicMock (return_value = str (tmp_path )),
289+ "download_file" : mock_download ,
290+ },
291+ ):
292+ result = await adapter .convert_message (update , _build_context ())
293+
294+ assert result is not None
295+ video_comp = next (c for c in result .message if isinstance (c , Comp .Video ))
296+ assert video_comp .file .startswith (str (tmp_path ))
297+ assert video_comp .path .startswith (str (tmp_path ))
298+ assert video_comp .file != "https://api.telegram.org/file/test/lesson.mp4"
299+
300+
301+ @pytest .mark .asyncio
302+ async def test_telegram_photo_downloads_to_local_temp_path (tmp_path ):
303+ """#9448: photo 组件必须拿到本地路径,而不是原始 Telegram file_path/URL。"""
304+ TelegramPlatformAdapter = _load_telegram_adapter ()
305+ adapter = TelegramPlatformAdapter (
306+ make_platform_config ("telegram" ),
307+ {},
308+ asyncio .Queue (),
309+ )
310+ photo = create_mock_file ("https://api.telegram.org/file/test/photo.jpg" )
311+ update = create_mock_update (message_text = None , photo = [photo ])
312+ convert_message_globals = adapter .convert_message .__func__ .__globals__
313+ mock_download = AsyncMock ()
314+
315+ with patch .dict (
316+ convert_message_globals ,
317+ {
318+ "get_astrbot_temp_path" : MagicMock (return_value = str (tmp_path )),
319+ "download_file" : mock_download ,
320+ },
321+ ):
322+ result = await adapter .convert_message (update , _build_context ())
323+
324+ assert result is not None
325+ image_comp = next (c for c in result .message if isinstance (c , Comp .Image ))
326+ assert image_comp .file .startswith (str (tmp_path ))
327+ assert image_comp .file != "https://api.telegram.org/file/test/photo.jpg"
328+
329+
330+ @pytest .mark .asyncio
331+ async def test_telegram_sticker_downloads_to_local_temp_path (tmp_path ):
332+ """#9448: sticker 组件必须拿到本地路径,而不是原始 Telegram file_path/URL。"""
333+ TelegramPlatformAdapter = _load_telegram_adapter ()
334+ adapter = TelegramPlatformAdapter (
335+ make_platform_config ("telegram" ),
336+ {},
337+ asyncio .Queue (),
338+ )
339+ sticker = create_mock_file ("https://api.telegram.org/file/test/sticker.webp" )
340+ sticker .emoji = "😀"
341+ update = create_mock_update (message_text = None , sticker = sticker )
342+ convert_message_globals = adapter .convert_message .__func__ .__globals__
343+ mock_download = AsyncMock ()
344+
345+ with patch .dict (
346+ convert_message_globals ,
347+ {
348+ "get_astrbot_temp_path" : MagicMock (return_value = str (tmp_path )),
349+ "download_file" : mock_download ,
350+ },
351+ ):
352+ result = await adapter .convert_message (update , _build_context ())
353+
354+ assert result is not None
355+ image_comp = next (c for c in result .message if isinstance (c , Comp .Image ))
356+ assert image_comp .file .startswith (str (tmp_path ))
357+ assert image_comp .file != "https://api.telegram.org/file/test/sticker.webp"
358+ assert result .message_str == "Sticker: 😀"
359+
360+
218361@pytest .mark .asyncio
219362async def test_telegram_voice_message_creates_record_component (tmp_path ):
220363 TelegramPlatformAdapter = _load_telegram_adapter ()
0 commit comments