ForcePilot/backend/test/unit/channel/outbound/test_downgrade.py
Kris bab30f2715
Some checks failed
Deploy VitePress site to Pages / build (push) Has been cancelled
Ruff Format Check / Ruff Format & Lint (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
feat:0715
2026-07-15 12:30:58 +08:00

142 lines
5.0 KiB
Python

from __future__ import annotations
from unittest.mock import patch
import pytest
from yuxi.channel.outbound.downgrade import Downgrader
from yuxi.channel.plugins.protocol import DeliveryCapabilities, OutboundMessage
@pytest.fixture
def text_message():
return OutboundMessage(content="hello", content_type="text")
@pytest.fixture
def markdown_message():
return OutboundMessage(content="**bold** and `code`", content_type="markdown")
@pytest.fixture
def interactive_message():
return OutboundMessage(
content={
"title": "Choose",
"description": "Pick one",
"options": [{"label": "A", "value": "a"}, {"label": "B", "value": "b"}],
"buttons": [{"label": "OK", "action_type": "postback", "value": "ok"}],
},
content_type="interactive",
)
@pytest.fixture
def media_message():
return OutboundMessage(content="see this", content_type="text", media=[{"url": "http://x/a.jpg"}])
async def test_text_unchanged_when_supported(text_message):
caps = DeliveryCapabilities(
max_text_length=100,
supports_markdown=False,
supports_interactive=False,
supports_media=False,
)
result = await Downgrader.downgrade(text_message, caps)
assert result.content == "hello"
assert result.content_type == "text"
async def test_markdown_downgraded_to_text(markdown_message):
caps = DeliveryCapabilities(supports_markdown=False, max_text_length=1000)
result = await Downgrader.downgrade(markdown_message, caps)
assert result.content_type == "text"
assert "**bold**" not in result.content
assert result.content == "bold and code"
async def test_markdown_kept_when_supported(markdown_message):
caps = DeliveryCapabilities(supports_markdown=True, max_text_length=1000)
result = await Downgrader.downgrade(markdown_message, caps)
assert result.content_type == "markdown"
assert result.content == "**bold** and `code`"
async def test_interactive_downgraded_to_markdown(interactive_message):
caps = DeliveryCapabilities(supports_interactive=False, supports_markdown=True, max_text_length=4000)
result = await Downgrader.downgrade(interactive_message, caps)
assert result.content_type == "markdown"
assert "Choose" in result.content
assert "A" in result.content
assert "OK" in result.content
async def test_interactive_downgraded_to_text_when_markdown_unsupported(interactive_message):
caps = DeliveryCapabilities(supports_interactive=False, supports_markdown=False, max_text_length=4000)
result = await Downgrader.downgrade(interactive_message, caps)
assert result.content_type == "text"
assert "Choose" in result.content
async def test_media_dropped_when_unsupported(media_message):
caps = DeliveryCapabilities(supports_media=False, max_text_length=4000)
result = await Downgrader.downgrade(media_message, caps)
assert result.media == []
assert "媒体附件" in result.content
async def test_media_kept_when_supported(media_message):
caps = DeliveryCapabilities(supports_media=True, max_text_length=4000)
result = await Downgrader.downgrade(media_message, caps)
assert result.media == media_message.media
assert "媒体附件" not in result.content
async def test_media_drop_without_existing_content():
msg = OutboundMessage(content="", content_type="text", media=[{"file": "a.jpg"}])
caps = DeliveryCapabilities(supports_media=False, max_text_length=4000)
result = await Downgrader.downgrade(msg, caps)
assert result.content.startswith("[该消息包含")
async def test_markdown_to_text_async_uses_thread_for_long_text():
long_text = "**bold**" + "x" * Downgrader._SYNC_REGEX_THRESHOLD
msg = OutboundMessage(content=long_text, content_type="markdown")
caps = DeliveryCapabilities(supports_markdown=False, max_text_length=100000)
with patch("asyncio.to_thread") as mock_to_thread:
mock_to_thread.return_value = "sync-result"
result = await Downgrader.downgrade(msg, caps)
mock_to_thread.assert_awaited_once()
assert result.content == "sync-result"
async def test_markdown_to_text_sync_for_short_text():
msg = OutboundMessage(content="**bold**", content_type="markdown")
caps = DeliveryCapabilities(supports_markdown=False, max_text_length=1000)
with patch("asyncio.to_thread") as mock_to_thread:
result = await Downgrader.downgrade(msg, caps)
mock_to_thread.assert_not_called()
assert result.content == "bold"
def test_interactive_to_markdown_string_passthrough():
result = Downgrader._interactive_to_markdown("plain")
assert result == "plain"
def test_interactive_to_markdown_list():
result = Downgrader._interactive_to_markdown([{"title": "A"}, {"title": "B"}])
assert "A" in result
assert "B" in result
def test_interactive_to_markdown_non_serializable():
class Unserializable:
def __str__(self):
return "unserializable-value"
result = Downgrader._interactive_to_markdown(Unserializable())
assert result == "unserializable-value"