|
11 | 11 |
|
12 | 12 | import pytest |
13 | 13 |
|
| 14 | +from chat_sdk.chat import Chat |
14 | 15 | from chat_sdk.testing import create_mock_adapter |
15 | 16 | from chat_sdk.types import Message |
16 | 17 |
|
@@ -184,3 +185,64 @@ async def handler(thread, message, channel=None, context=None): |
184 | 185 | await chat.handle_incoming_message(adapter, dm_thread_id, msg) |
185 | 186 |
|
186 | 187 | assert len(calls) == 0 |
| 188 | + |
| 189 | + @pytest.mark.asyncio |
| 190 | + async def test_dm_handler_runs_before_pattern_handler(self): |
| 191 | + """DM handler takes precedence over ``on_message`` pattern handlers. |
| 192 | +
|
| 193 | + Mirrors the routing-precedence clarification in vercel/chat#491 — DM |
| 194 | + handlers run before subscribed/mention/pattern handlers when registered. |
| 195 | + Without this precedence a DM whose text matches a registered pattern |
| 196 | + would fire both handlers (or only the pattern handler), silently |
| 197 | + breaking DM-only flows. |
| 198 | + """ |
| 199 | + chat, adapters, state = await create_chat() |
| 200 | + adapter = adapters["slack"] |
| 201 | + dm_calls: list[Message] = [] |
| 202 | + pattern_calls: list[Message] = [] |
| 203 | + |
| 204 | + @chat.on_direct_message |
| 205 | + async def dm_handler(thread, message, channel=None, context=None): |
| 206 | + dm_calls.append(message) |
| 207 | + |
| 208 | + @chat.on_message(r"^!help") |
| 209 | + async def pattern_handler(thread, message, context=None): |
| 210 | + pattern_calls.append(message) |
| 211 | + |
| 212 | + dm_thread_id = "slack:DDMCHAN:" |
| 213 | + msg = create_msg("!help me please", thread_id=dm_thread_id) |
| 214 | + await chat.handle_incoming_message(adapter, dm_thread_id, msg) |
| 215 | + |
| 216 | + assert len(dm_calls) == 1 |
| 217 | + assert dm_calls[0].text == "!help me please" |
| 218 | + assert pattern_calls == [] |
| 219 | + |
| 220 | + |
| 221 | +class TestDMRoutingDocs: |
| 222 | + """Lock in the precedence-clarification docstrings ported from vercel/chat#491. |
| 223 | +
|
| 224 | + The runtime routing was already correct in the Python port (see |
| 225 | + ``Chat._handle_incoming_message``: DM handlers run before |
| 226 | + subscribed/mention/pattern). These checks fail if a future doc rewrite drops |
| 227 | + the precedence wording, which would let the docs drift out of sync with |
| 228 | + upstream's clarified contract again (the original issue closed by #491). |
| 229 | + """ |
| 230 | + |
| 231 | + def test_on_direct_message_docstring_documents_precedence(self): |
| 232 | + """``Chat.on_direct_message`` docstring states DM handlers run first.""" |
| 233 | + doc = Chat.on_direct_message.__doc__ or "" |
| 234 | + assert "before" in doc.lower() |
| 235 | + assert "on_subscribed_message" in doc |
| 236 | + assert "on_mention" in doc |
| 237 | + # Falls-through-when-unregistered carve-out preserved. |
| 238 | + assert "backward compatibility" in doc |
| 239 | + |
| 240 | + def test_thread_subscribe_docstring_documents_dm_carveout(self): |
| 241 | + """``ThreadImpl.subscribe`` docstring covers the DM precedence carve-out.""" |
| 242 | + from chat_sdk.thread import ThreadImpl |
| 243 | + |
| 244 | + doc = ThreadImpl.subscribe.__doc__ or "" |
| 245 | + assert "non-DM" in doc |
| 246 | + assert "on_direct_message" in doc |
| 247 | + # Pre-existing invariant: initial subscribing message does NOT fire. |
| 248 | + assert "NOT fire" in doc |
0 commit comments