|
|
@@ -113,6 +113,7 @@ from adapter import (
|
|
|
_handle_chatto_list_room_attachments,
|
|
|
_handle_chatto_list_room_groups,
|
|
|
_handle_chatto_list_rooms,
|
|
|
+ _handle_chatto_delete_message,
|
|
|
_handle_chatto_mark_thread_as_read,
|
|
|
_handle_chatto_pin_message,
|
|
|
_handle_chatto_search_messages,
|
|
|
@@ -5506,6 +5507,7 @@ class TestPluginToolSchemas:
|
|
|
"chatto_list_room_attachments",
|
|
|
"chatto_list_pinned_messages",
|
|
|
"chatto_pin_message",
|
|
|
+ "chatto_delete_message",
|
|
|
"chatto_set_custom_status",
|
|
|
"chatto_delete_custom_status",
|
|
|
"chatto_fetch_link_preview",
|
|
|
@@ -5548,6 +5550,7 @@ class TestPluginToolHandlers:
|
|
|
client.list_pinned_messages = AsyncMock(return_value=PinnedMessagesPage())
|
|
|
client.pin_message = AsyncMock()
|
|
|
client.unpin_message = AsyncMock(return_value=True)
|
|
|
+ client.delete_message = AsyncMock(return_value=True)
|
|
|
client.set_custom_status = AsyncMock(return_value={})
|
|
|
client.delete_custom_status = AsyncMock(return_value={})
|
|
|
client.fetch_link_preview = AsyncMock(return_value=(None, ""))
|
|
|
@@ -7199,6 +7202,98 @@ class TestPluginToolHandlers:
|
|
|
assert result["unpinned"] is True
|
|
|
client.unpin_message.assert_awaited_once_with("room-1", "msg-1")
|
|
|
|
|
|
+ # -- chatto_delete_message --
|
|
|
+
|
|
|
+ async def test_delete_message_refuses_a_read_only_room(self):
|
|
|
+ """Deleting changes what the room shows everyone, so it is a write: the
|
|
|
+ read-only promise for unlisted channels has to hold here too."""
|
|
|
+ room = _make_room("room-1", "Ops", RoomKind.CHANNEL)
|
|
|
+ client = self._stub_client()
|
|
|
+ client.get_room = AsyncMock(return_value=_make_room_state(room))
|
|
|
+ result, _, _ = await self._run(
|
|
|
+ _handle_chatto_delete_message,
|
|
|
+ {"room_id": "room-1", "event_id": "msg-1"},
|
|
|
+ client=client,
|
|
|
+ )
|
|
|
+ assert result["success"] is False
|
|
|
+ assert "read-only" in result["error"]
|
|
|
+ client.delete_message.assert_not_awaited()
|
|
|
+
|
|
|
+ async def test_delete_message_refuses_a_room_the_bot_has_not_joined(self):
|
|
|
+ """A delete is a write, and the refusal names the action it blocked."""
|
|
|
+ room = _make_room("room-1", "Ops", RoomKind.CHANNEL)
|
|
|
+ client = self._stub_client()
|
|
|
+ client.get_room = AsyncMock(
|
|
|
+ return_value=_make_room_state(room, is_member=False)
|
|
|
+ )
|
|
|
+ result, _, _ = await self._run(
|
|
|
+ _handle_chatto_delete_message,
|
|
|
+ {"room_id": "room-1", "event_id": "msg-1"},
|
|
|
+ client=client,
|
|
|
+ optional_mention_rooms="room-1",
|
|
|
+ )
|
|
|
+
|
|
|
+ assert result["success"] is False
|
|
|
+ assert "cannot delete a message there" in result["error"]
|
|
|
+ assert "/join room-1" in result["error"]
|
|
|
+ client.delete_message.assert_not_awaited()
|
|
|
+
|
|
|
+ async def test_delete_message_requires_an_event_id(self):
|
|
|
+ room = _make_room("room-1", "Ops", RoomKind.CHANNEL)
|
|
|
+ client = self._stub_client()
|
|
|
+ client.get_room = AsyncMock(return_value=_make_room_state(room))
|
|
|
+ result, _, _ = await self._run(
|
|
|
+ _handle_chatto_delete_message,
|
|
|
+ {"room_id": "room-1"},
|
|
|
+ client=client,
|
|
|
+ optional_mention_rooms="room-1",
|
|
|
+ )
|
|
|
+ assert result["success"] is False
|
|
|
+ assert "event_id" in result["error"]
|
|
|
+ client.delete_message.assert_not_awaited()
|
|
|
+
|
|
|
+ async def test_delete_message_refuses_missing_room(self):
|
|
|
+ client = self._stub_client()
|
|
|
+ result, _, _ = await self._run(
|
|
|
+ _handle_chatto_delete_message, {"event_id": "msg-1"}, client=client
|
|
|
+ )
|
|
|
+ assert result["success"] is False
|
|
|
+ assert "room_id" in result["error"]
|
|
|
+ client.delete_message.assert_not_awaited()
|
|
|
+
|
|
|
+ async def test_delete_message_deletes_in_an_opted_in_channel(self):
|
|
|
+ room = _make_room("room-1", "Ops", RoomKind.CHANNEL)
|
|
|
+ client = self._stub_client()
|
|
|
+ client.get_room = AsyncMock(return_value=_make_room_state(room))
|
|
|
+ client.delete_message = AsyncMock(return_value=True)
|
|
|
+ result, _, _ = await self._run(
|
|
|
+ _handle_chatto_delete_message,
|
|
|
+ {"room_id": "room-1", "event_id": "msg-1"},
|
|
|
+ client=client,
|
|
|
+ optional_mention_rooms="room-1",
|
|
|
+ )
|
|
|
+ assert result["success"] is True
|
|
|
+ assert result["deleted"] is True
|
|
|
+ client.delete_message.assert_awaited_once_with("room-1", "msg-1")
|
|
|
+
|
|
|
+ async def test_delete_message_reports_the_server_refusal(self):
|
|
|
+ """The server decides who may delete what — a bot deleting someone
|
|
|
+ else's message gets the server's error back, not a fake success."""
|
|
|
+ room = _make_room("room-1", "Ops", RoomKind.CHANNEL)
|
|
|
+ client = self._stub_client()
|
|
|
+ client.get_room = AsyncMock(return_value=_make_room_state(room))
|
|
|
+ client.delete_message = AsyncMock(
|
|
|
+ side_effect=ChattoError("permission_denied: not the message author")
|
|
|
+ )
|
|
|
+ result, _, _ = await self._run(
|
|
|
+ _handle_chatto_delete_message,
|
|
|
+ {"room_id": "room-1", "event_id": "msg-1"},
|
|
|
+ client=client,
|
|
|
+ optional_mention_rooms="room-1",
|
|
|
+ )
|
|
|
+ assert result["success"] is False
|
|
|
+ assert "Chatto API error" in result["error"]
|
|
|
+
|
|
|
# -- chatto_send_ephemeral --
|
|
|
|
|
|
async def test_send_ephemeral_posts_then_schedules_the_ttl(self):
|