142 lines
5.0 KiB
Python
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"
|