106 lines
3.6 KiB
Python
106 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import AsyncIterator
|
|
from types import SimpleNamespace
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from opensquilla.channels.dingtalk import DingTalkChannel, DingTalkChannelConfig
|
|
|
|
|
|
def _sdk_message(msg_id: str, conversation_id: str, sender_id: str, webhook: str) -> Any:
|
|
return SimpleNamespace(
|
|
message_id=msg_id,
|
|
message_type="text",
|
|
text=SimpleNamespace(content="hello"),
|
|
sender_staff_id=sender_id,
|
|
sender_nick=sender_id,
|
|
conversation_id=conversation_id,
|
|
conversation_type="1",
|
|
session_webhook=webhook,
|
|
)
|
|
|
|
|
|
async def test_dingtalk_reply_targets_triggering_message_not_latest_inbound() -> None:
|
|
channel = DingTalkChannel(DingTalkChannelConfig(name="dingtalk"))
|
|
|
|
raw_a = _sdk_message("msg-a", "conv-a", "staff-a", "https://example.invalid/hook-a")
|
|
raw_b = _sdk_message("msg-b", "conv-b", "staff-b", "https://example.invalid/hook-b")
|
|
|
|
incoming_a = channel.parse_message(raw_a)
|
|
assert incoming_a is not None
|
|
channel._last_incoming = raw_a
|
|
|
|
# A message from another conversation arrives before A's reply is sent.
|
|
assert channel.parse_message(raw_b) is not None
|
|
channel._last_incoming = raw_b
|
|
|
|
delivered: list[tuple[str, Any]] = []
|
|
channel._handler = SimpleNamespace(
|
|
reply_text=lambda content, incoming_message: delivered.append((content, incoming_message))
|
|
)
|
|
|
|
reply = channel.build_reply_message("answer for a", incoming_a)
|
|
assert reply.metadata["dingtalk_reply_msg_id"] == "msg-a"
|
|
|
|
await channel.send(reply)
|
|
|
|
assert delivered == [("answer for a", raw_a)]
|
|
|
|
|
|
def test_dingtalk_streaming_reply_kwargs_pin_triggering_message() -> None:
|
|
channel = DingTalkChannel(DingTalkChannelConfig(name="dingtalk"))
|
|
|
|
incoming = channel.parse_message(
|
|
_sdk_message("msg-1", "conv-1", "staff-1", "https://example.invalid/hook-1")
|
|
)
|
|
|
|
assert incoming is not None
|
|
assert channel.streaming_reply_kwargs(incoming) == {"reply_msg_id": "msg-1"}
|
|
|
|
|
|
async def test_dingtalk_expired_explicit_reply_context_fails_closed() -> None:
|
|
channel = DingTalkChannel(DingTalkChannelConfig(name="dingtalk"))
|
|
raw_original = _sdk_message(
|
|
"msg-original", "conv-original", "staff-original", "https://example.invalid/original"
|
|
)
|
|
incoming_original = channel.parse_message(raw_original)
|
|
assert incoming_original is not None
|
|
|
|
latest = raw_original
|
|
for index in range(257):
|
|
latest = _sdk_message(
|
|
f"msg-{index}",
|
|
f"conv-{index}",
|
|
f"staff-{index}",
|
|
f"https://example.invalid/{index}",
|
|
)
|
|
assert channel.parse_message(latest) is not None
|
|
channel._last_incoming = latest
|
|
|
|
delivered: list[tuple[str, Any]] = []
|
|
channel._handler = SimpleNamespace(
|
|
reply_text=lambda content, incoming_message: delivered.append((content, incoming_message))
|
|
)
|
|
reply = channel.build_reply_message("late answer", incoming_original)
|
|
|
|
with pytest.raises(RuntimeError, match="reply context.*expired"):
|
|
await channel.send(reply)
|
|
|
|
assert delivered == []
|
|
|
|
|
|
async def test_dingtalk_streaming_expired_explicit_reply_context_fails_closed() -> None:
|
|
channel = DingTalkChannel(DingTalkChannelConfig(name="dingtalk"))
|
|
channel._client = object()
|
|
channel._last_incoming = _sdk_message(
|
|
"msg-latest", "conv-latest", "staff-latest", "https://example.invalid/latest"
|
|
)
|
|
|
|
async def _chunks() -> AsyncIterator[str]:
|
|
yield "must not be sent"
|
|
|
|
with pytest.raises(RuntimeError, match="reply context.*expired"):
|
|
await channel.send_streaming(_chunks(), reply_msg_id="msg-expired")
|