|
@@ -1215,6 +1215,98 @@ class TestReactionForwarding:
|
|
|
await adapter._handle_realtime_event(event)
|
|
await adapter._handle_realtime_event(event)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# -- Interactive prompt reactions --
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class TestInteractiveReactionRouting:
|
|
|
|
|
+ """A reaction on a prompt card resolves it; the bot's own echo does not.
|
|
|
|
|
+
|
|
|
|
|
+ Routing sits behind the envelope's self-event filter, so a reaction the
|
|
|
|
|
+ bot itself adds can never answer the prompt it decorates — the echo rule
|
|
|
|
|
+ and the prompt rule have to hold together, which is what these pin.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ @pytest_asyncio.fixture
|
|
|
|
|
+ def adapter(self):
|
|
|
|
|
+ adapter = _make_adapter(allowed_users="human-1")
|
|
|
|
|
+ adapter.me = _make_user("bot-user-id", "hermes_bot")
|
|
|
|
|
+ adapter.edit_message = AsyncMock()
|
|
|
|
|
+ return adapter
|
|
|
|
|
+
|
|
|
|
|
+ def _event(self, emoji, *, actor_id="human-1", message_id="prompt-1"):
|
|
|
|
|
+ event = MagicMock()
|
|
|
|
|
+ event.id = f"evt-{emoji}-{actor_id}"
|
|
|
|
|
+ event.kind = "reaction_added"
|
|
|
|
|
+ event.actor_id = actor_id
|
|
|
|
|
+ event.payload = MagicMock(
|
|
|
|
|
+ room_id="room-1", message_event_id=message_id, emoji=emoji
|
|
|
|
|
+ )
|
|
|
|
|
+ return event
|
|
|
|
|
+
|
|
|
|
|
+ def _clarify(self, adapter):
|
|
|
|
|
+ from adapter import _ClarifyPrompt
|
|
|
|
|
+
|
|
|
|
|
+ prompt = _ClarifyPrompt(
|
|
|
|
|
+ clarify_id="clarify-1",
|
|
|
|
|
+ choices=["yes", "no"],
|
|
|
|
|
+ session_key="session-1",
|
|
|
|
|
+ chat_id="room-1",
|
|
|
|
|
+ message_id="prompt-1",
|
|
|
|
|
+ expires_at=float("inf"),
|
|
|
|
|
+ )
|
|
|
|
|
+ adapter._clarify_prompts["prompt-1"] = prompt
|
|
|
|
|
+ adapter._clarify_id_to_message["clarify-1"] = "prompt-1"
|
|
|
|
|
+ return prompt
|
|
|
|
|
+
|
|
|
|
|
+ async def test_a_numbered_reaction_resolves_the_clarify(self, adapter):
|
|
|
|
|
+ self._clarify(adapter)
|
|
|
|
|
+ resolve = MagicMock()
|
|
|
|
|
+ with patch("tools.clarify_gateway.resolve_gateway_clarify", resolve):
|
|
|
|
|
+ await adapter._handle_realtime_event(self._event("2️⃣"))
|
|
|
|
|
+
|
|
|
|
|
+ resolve.assert_called_once_with("clarify-1", "no")
|
|
|
|
|
+ assert "prompt-1" not in adapter._clarify_prompts
|
|
|
|
|
+ assert "clarify-1" not in adapter._clarify_id_to_message
|
|
|
|
|
+
|
|
|
|
|
+ async def test_the_bots_own_reaction_does_not_resolve_its_prompt(self, adapter):
|
|
|
|
|
+ """The self-event filter runs before routing, so an echo of the bot's
|
|
|
|
|
+ own marker cannot answer the prompt it decorates."""
|
|
|
|
|
+ self._clarify(adapter)
|
|
|
|
|
+ resolve = MagicMock()
|
|
|
|
|
+ with patch("tools.clarify_gateway.resolve_gateway_clarify", resolve):
|
|
|
|
|
+ await adapter._handle_realtime_event(
|
|
|
|
|
+ self._event("2️⃣", actor_id="bot-user-id")
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ resolve.assert_not_called()
|
|
|
|
|
+ assert "prompt-1" in adapter._clarify_prompts
|
|
|
|
|
+
|
|
|
|
|
+ async def test_an_unauthorized_reaction_leaves_the_prompt_live(self, adapter):
|
|
|
|
|
+ """Auth is fail-closed here too: a stranger's reaction is consumed but
|
|
|
|
|
+ answers nothing."""
|
|
|
|
|
+ self._clarify(adapter)
|
|
|
|
|
+ resolve = MagicMock()
|
|
|
|
|
+ with patch("tools.clarify_gateway.resolve_gateway_clarify", resolve):
|
|
|
|
|
+ await adapter._handle_realtime_event(
|
|
|
|
|
+ self._event("2️⃣", actor_id="stranger-9")
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ resolve.assert_not_called()
|
|
|
|
|
+ assert "prompt-1" in adapter._clarify_prompts
|
|
|
|
|
+
|
|
|
|
|
+ async def test_a_reaction_on_an_untracked_message_is_forwarded(self, adapter):
|
|
|
|
|
+ """Only prompt cards are consumed — every other reaction still reaches
|
|
|
|
|
+ the gateway's reaction hook."""
|
|
|
|
|
+ handler = AsyncMock()
|
|
|
|
|
+ adapter.set_reaction_handler(handler)
|
|
|
|
|
+
|
|
|
|
|
+ await adapter._handle_realtime_event(
|
|
|
|
|
+ self._event("👍", message_id="ordinary-msg")
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ handler.assert_called_once()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# -- chat_type mapping --
|
|
# -- chat_type mapping --
|
|
|
|
|
|
|
|
|
|
|