diff --git a/gateway/platforms/matrix.py b/gateway/platforms/matrix.py index ac1362cda..768368354 100644 --- a/gateway/platforms/matrix.py +++ b/gateway/platforms/matrix.py @@ -1502,7 +1502,8 @@ class MatrixAdapter(BasePlatformAdapter): reaction_key = (room_id, msg_id) if reaction_key in self._pending_reactions: eyes_event_id = self._pending_reactions.pop(reaction_key) - await self._redact_reaction(room_id, eyes_event_id) + if not await self._redact_reaction(room_id, eyes_event_id): + logger.debug("Matrix: failed to redact eyes reaction %s", eyes_event_id) await self._send_reaction( room_id, msg_id, diff --git a/tests/gateway/test_matrix.py b/tests/gateway/test_matrix.py index aa7309fe9..1a480570e 100644 --- a/tests/gateway/test_matrix.py +++ b/tests/gateway/test_matrix.py @@ -2001,6 +2001,28 @@ class TestMatrixReactions: self.adapter._redact_reaction.assert_called_once_with("!room:ex", "$eyes_reaction_123") self.adapter._send_reaction.assert_called_once_with("!room:ex", "$msg1", "✅") + @pytest.mark.asyncio + async def test_on_processing_complete_sends_cross_on_failure(self): + from gateway.platforms.base import MessageEvent, MessageType, ProcessingOutcome + + self.adapter._reactions_enabled = True + self.adapter._pending_reactions = {("!room:ex", "$msg1"): "$eyes_reaction_123"} + self.adapter._redact_reaction = AsyncMock(return_value=True) + self.adapter._send_reaction = AsyncMock(return_value="$cross_reaction_456") + + source = MagicMock() + source.chat_id = "!room:ex" + event = MessageEvent( + text="hello", + message_type=MessageType.TEXT, + source=source, + raw_message={}, + message_id="$msg1", + ) + await self.adapter.on_processing_complete(event, ProcessingOutcome.FAILURE) + self.adapter._redact_reaction.assert_called_once_with("!room:ex", "$eyes_reaction_123") + self.adapter._send_reaction.assert_called_once_with("!room:ex", "$msg1", "❌") + @pytest.mark.asyncio async def test_on_processing_complete_cancelled_sends_no_terminal_reaction(self): from gateway.platforms.base import MessageEvent, MessageType, ProcessingOutcome @@ -2020,6 +2042,29 @@ class TestMatrixReactions: await self.adapter.on_processing_complete(event, ProcessingOutcome.CANCELLED) self.adapter._send_reaction.assert_not_called() + @pytest.mark.asyncio + async def test_on_processing_complete_no_pending_reaction(self): + """on_processing_complete should skip redaction if no eyes reaction was tracked.""" + from gateway.platforms.base import MessageEvent, MessageType, ProcessingOutcome + + self.adapter._reactions_enabled = True + self.adapter._pending_reactions = {} + self.adapter._redact_reaction = AsyncMock() + self.adapter._send_reaction = AsyncMock(return_value="$check_reaction_789") + + source = MagicMock() + source.chat_id = "!room:ex" + event = MessageEvent( + text="hello", + message_type=MessageType.TEXT, + source=source, + raw_message={}, + message_id="$msg1", + ) + await self.adapter.on_processing_complete(event, ProcessingOutcome.SUCCESS) + self.adapter._redact_reaction.assert_not_called() + self.adapter._send_reaction.assert_called_once_with("!room:ex", "$msg1", "✅") + @pytest.mark.asyncio async def test_reactions_disabled(self): from gateway.platforms.base import MessageEvent, MessageType