|
|
@@ -205,6 +205,9 @@ class TestEmojiShortcode:
|
|
|
assert _EMOJI_TO_SHORTCODE.get("❤") == "heart"
|
|
|
assert _EMOJI_TO_SHORTCODE.get("✅") == "white_check_mark"
|
|
|
assert _EMOJI_TO_SHORTCODE.get("❌") == "x"
|
|
|
+ # Sent when a message addresses someone else — without the mapping the
|
|
|
+ # raw emoji would go out as a shortcode and the server would reject it.
|
|
|
+ assert _EMOJI_TO_SHORTCODE.get("🫥") == "dotted_line_face"
|
|
|
|
|
|
|
|
|
# -- Adapter instantiation and properties --
|
|
|
@@ -849,6 +852,79 @@ class TestPresence:
|
|
|
client.update_presence.assert_not_called()
|
|
|
|
|
|
|
|
|
+# -- Mentions of other people --
|
|
|
+
|
|
|
+class TestForeignMention:
|
|
|
+ """With require_mention off the bot reads everything, so a message aimed at
|
|
|
+ a named colleague would otherwise get an unsolicited answer. Acknowledge it
|
|
|
+ with 🫥 and stay out of the conversation."""
|
|
|
+
|
|
|
+ def _adapter(self, **overrides):
|
|
|
+ adapter = _make_adapter()
|
|
|
+ adapter.chatto_config.allow_all_users.value = True
|
|
|
+ adapter.chatto_config.require_mention.value = False
|
|
|
+ adapter.chatto_config.reactions.value = True
|
|
|
+ for key, value in overrides.items():
|
|
|
+ getattr(adapter.chatto_config, key).value = value
|
|
|
+ adapter.me = _make_user("bot-user-id", "hermes_bot")
|
|
|
+ adapter._user_cache["user-1"] = _make_user("user-1", "alice")
|
|
|
+ adapter.handle_message = AsyncMock()
|
|
|
+ adapter.add_reaction = AsyncMock(return_value=True)
|
|
|
+ adapter._room_kinds["room-1"] = RoomKind.CHANNEL
|
|
|
+ adapter._room_kinds["dm-1"] = RoomKind.DM
|
|
|
+ return adapter
|
|
|
+
|
|
|
+ async def _dispatch(self, adapter, body, room_id="room-1"):
|
|
|
+ payload = _make_posted_payload(room_id=room_id)
|
|
|
+ payload.fetch_message = AsyncMock(
|
|
|
+ return_value=_make_message(body=body, room_id=room_id))
|
|
|
+ await adapter._dispatch_message_posted(payload)
|
|
|
+
|
|
|
+ async def test_message_for_someone_else_is_only_acknowledged(self):
|
|
|
+ adapter = self._adapter()
|
|
|
+ await self._dispatch(adapter, "@bob can you take a look?")
|
|
|
+ adapter.handle_message.assert_not_called()
|
|
|
+ adapter.add_reaction.assert_awaited_once()
|
|
|
+ assert adapter.add_reaction.await_args.args[2] == "🫥"
|
|
|
+
|
|
|
+ async def test_being_mentioned_alongside_someone_else_still_answers(self):
|
|
|
+ adapter = self._adapter()
|
|
|
+ await self._dispatch(adapter, "@bob and @hermes_bot, thoughts?")
|
|
|
+ adapter.handle_message.assert_called_once()
|
|
|
+ adapter.add_reaction.assert_not_awaited()
|
|
|
+
|
|
|
+ async def test_broadcast_mentions_address_the_bot_too(self):
|
|
|
+ adapter = self._adapter()
|
|
|
+ for body in ("@here standup in 5", "@channel heads up", "@everyone hi"):
|
|
|
+ adapter.handle_message.reset_mock()
|
|
|
+ await self._dispatch(adapter, body)
|
|
|
+ adapter.handle_message.assert_called_once()
|
|
|
+
|
|
|
+ async def test_plain_message_is_unaffected(self):
|
|
|
+ adapter = self._adapter()
|
|
|
+ await self._dispatch(adapter, "how do I reset the cache?")
|
|
|
+ adapter.handle_message.assert_called_once()
|
|
|
+
|
|
|
+ async def test_dms_are_answered_even_when_they_name_someone_else(self):
|
|
|
+ adapter = self._adapter()
|
|
|
+ await self._dispatch(adapter, "@bob said the build is red", room_id="dm-1")
|
|
|
+ adapter.handle_message.assert_called_once()
|
|
|
+
|
|
|
+ async def test_require_mention_keeps_discarding_without_a_reaction(self):
|
|
|
+ """The older gate wins: it drops the message before we get here, and it
|
|
|
+ deliberately says nothing at all."""
|
|
|
+ adapter = self._adapter(require_mention=True)
|
|
|
+ await self._dispatch(adapter, "@bob can you take a look?")
|
|
|
+ adapter.handle_message.assert_not_called()
|
|
|
+ adapter.add_reaction.assert_not_awaited()
|
|
|
+
|
|
|
+ async def test_silence_holds_when_reactions_are_disabled(self):
|
|
|
+ adapter = self._adapter(reactions=False)
|
|
|
+ await self._dispatch(adapter, "@bob can you take a look?")
|
|
|
+ adapter.handle_message.assert_not_called()
|
|
|
+ adapter.add_reaction.assert_not_awaited()
|
|
|
+
|
|
|
+
|
|
|
# -- require_mention --
|
|
|
|
|
|
class TestRequireMention:
|