Files
AstrBot/tests/test_mattermost_adapter.py
Weilong Liao 7c366a708b fix: unify media reference handling (#8764)
* fix: unify media reference handling

* fix: accept bare base64 record media refs

* chore: update agents.md

* fix: unify file URI handling across media components and utilities

* fix: unify media reference type handling with MediaRefStr alias

* Potential fix for pull request finding 'CodeQL / Incomplete URL substring sanitization'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Update astrbot/core/platform/sources/discord/discord_platform_adapter.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* fix: unify media handling and improve base64 decoding across components

* fix: simplify client_kwargs type definition and enhance media message handling in platform adapter

* fix: unify media utility documentation and enhance function descriptions

* perf: drop "pilk" requirement, improve audio outbound for tencent-related IM apps which using silk

* fix: unify Tencent Silk audio handling and enhance media resolver functionality

---

- Centralize media reference materialization and base64 resolution for local paths, http(s), base64://, data URIs, and legacy bare base64 payloads.
- Normalize incoming Record audio to wav and Image media to temporary jpg during preprocess, with event-scoped cleanup.
- Reuse the shared media resolver across OpenAI, Gemini, Anthropic, MiMo, DeerFlow, STT, and platform media paths while sanitizing logs and cleaning temporary conversion outputs.
- Ensure generated TTS audio is tracked for cleanup after the event finishes.

fix #8676
fix #8543
fix #7588
fix #7580
fix #8030
fix #8034
fix #7461
fix #7565
fix #6509
fix #7144
fix #7795



---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-06-14 10:37:16 +08:00

114 lines
3.7 KiB
Python

import asyncio
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import astrbot.api.message_components as Comp
from astrbot.core.platform.sources.mattermost.client import MattermostClient
from astrbot.core.platform.sources.mattermost.mattermost_adapter import (
MattermostPlatformAdapter,
)
from tests.fixtures.helpers import make_platform_config
def _build_adapter() -> MattermostPlatformAdapter:
adapter = MattermostPlatformAdapter(
make_platform_config(
"mattermost",
id="test_mattermost",
mattermost_url="https://chat.example.com",
mattermost_bot_token="test_token",
mattermost_reconnect_delay=5.0,
),
{},
asyncio.Queue(),
)
adapter.bot_self_id = "bot-id"
adapter.bot_username = "bot"
adapter._mention_pattern = adapter._build_mention_pattern(adapter.bot_username)
return adapter
@pytest.mark.asyncio
async def test_mattermost_convert_message_strips_leading_self_mention():
adapter = _build_adapter()
result = await adapter.convert_message(
post={
"id": "post-1",
"channel_id": "channel-1",
"user_id": "user-1",
"message": "@bot /help now",
"create_at": 1_700_000_000_000,
"file_ids": [],
},
data={
"channel_type": "O",
"sender_name": "alice",
},
)
assert result is not None
assert result.message_str == "/help now"
assert isinstance(result.message[0], Comp.At)
assert result.message[0].qq == "bot-id"
assert any(
isinstance(component, Comp.Plain) and component.text.strip() == "/help now"
for component in result.message
)
@pytest.mark.asyncio
async def test_mattermost_parse_post_attachments_maps_media_types(tmp_path):
client = MattermostClient("https://chat.example.com", "test_token")
wav_path = str(tmp_path / "mattermost_voice.wav")
file_infos = {
"img": {"name": "image.png", "mime_type": "image/png"},
"audio": {"name": "voice.ogg", "mime_type": "audio/ogg"},
"video": {"name": "clip.mp4", "mime_type": "video/mp4"},
"doc": {"name": "report.pdf", "mime_type": "application/pdf"},
}
client.get_file_info = AsyncMock(side_effect=lambda file_id: file_infos[file_id])
client.download_file = AsyncMock(return_value=b"payload")
class FakeMediaResolver:
def __init__(self, media_ref: str, **kwargs) -> None:
assert media_ref.endswith("mattermost_audio.ogg")
assert kwargs["media_type"] == "audio"
async def to_path(self, **kwargs) -> str:
assert kwargs["target_format"] == "wav"
return wav_path
with (
patch(
"astrbot.core.platform.sources.mattermost.client.get_astrbot_temp_path",
MagicMock(return_value=str(tmp_path)),
),
patch(
"astrbot.core.platform.sources.mattermost.client.MediaResolver",
FakeMediaResolver,
),
):
components, temp_paths = await client.parse_post_attachments(
["img", "audio", "video", "doc"]
)
assert len(components) == 4
assert isinstance(components[0], Comp.Image)
assert isinstance(components[1], Comp.Record)
assert components[1].file == wav_path
assert components[1].url == wav_path
assert isinstance(components[2], Comp.Video)
assert isinstance(components[3], Comp.File)
assert len(temp_paths) == 4
expected_names = ["image.png", "voice.ogg", "clip.mp4", "report.pdf"]
for temp_path, expected_name in zip(temp_paths, expected_names):
path = Path(temp_path)
assert path.exists()
assert path.name.endswith(Path(expected_name).suffix)