From 76717cdb693599da71773d3b80fd9ffcc31d4b51 Mon Sep 17 00:00:00 2001 From: Patrick Skillen Date: Sat, 15 Mar 2025 23:43:14 +0000 Subject: [PATCH 1/3] fix: update reaction method to use react_in_channel --- src/responders/message_reaction_responder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/responders/message_reaction_responder.py b/src/responders/message_reaction_responder.py index 393ad5d..ef426ff 100644 --- a/src/responders/message_reaction_responder.py +++ b/src/responders/message_reaction_responder.py @@ -22,7 +22,7 @@ def handle_packet(self, packet: MeshPacket) -> None: emoji = random.choice(self.emoji) # Respond to the message - self.react_to(packet, emoji) + self.react_in_channel(packet, emoji) def _is_enrolled(self, from_id: str) -> bool: user_prefs = self.bot.user_prefs_persistence.get_user_prefs(from_id) From 7a0cefe22b21af5fee712dffee8ae119cfcfbb6a Mon Sep 17 00:00:00 2001 From: Patrick Skillen Date: Sun, 16 Mar 2025 13:03:17 +0000 Subject: [PATCH 2/3] refactor(tests): most common test stuff to BaseFeatureTestCase --- test/__init__.py | 61 +++++++++++++++++++++++++++++++++++++ test/commands/__init__.py | 48 ++--------------------------- test/responders/__init__.py | 26 ++-------------- 3 files changed, 66 insertions(+), 69 deletions(-) diff --git a/test/__init__.py b/test/__init__.py index e69de29..1b6ba50 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -0,0 +1,61 @@ +import unittest +from abc import ABC +from unittest.mock import Mock + +from src.bot import MeshtasticBot +from src.data_classes import MeshNode +from test.test_setup_data import get_test_bot + + +class BaseFeatureTestCase(unittest.TestCase, ABC): + bot: MeshtasticBot + mock_interface: Mock + test_admin_nodes: list[MeshNode] = [] + test_non_admin_nodes: list[MeshNode] = [] + test_nodes: list[MeshNode] = [] + + def setUp(self): + self.bot, self.test_non_admin_nodes, self.test_admin_nodes = get_test_bot() + self.test_nodes = self.test_non_admin_nodes + self.test_admin_nodes + self.mock_interface = self.bot.interface = Mock() + + def assert_message_sent(self, expected_response: str, to: MeshNode, want_ack: bool = False, multi_response=False): + if multi_response: + self.mock_interface.sendText.assert_called() + + # Strip the newline character from the expected response + expected_response = expected_response.strip() + + # Assert that one of the calls matches the expected + for call_args in self.mock_interface.sendText.call_args_list: + if (call_args[1]['destinationId'] == to.user.id + and call_args[1]['wantAck'] == want_ack + and call_args[0][0].strip() == expected_response): + return + + self.fail( + f"Expected response with destinationId {to.user.id} and wantAck {want_ack}: \n" + f"{expected_response}\n" + f"\n" + f"not found in calls:\n" + f"{self.mock_interface.sendText.call_args_list}") + else: + self.mock_interface.sendText.assert_called_once_with( + expected_response, + destinationId=to.user.id, + wantAck=want_ack + ) + + def assert_reaction_sent(self, emoji: str, reply_id: int, channel=0, sender_id: str = None): + if sender_id: + self.mock_interface.sendReaction.assert_called_once_with( + emoji, + messageId=reply_id, + destinationId=sender_id + ) + else: + self.mock_interface.sendReaction.assert_called_once_with( + emoji, + messageId=reply_id, + channelIndex=channel, + ) diff --git a/test/commands/__init__.py b/test/commands/__init__.py index 7d68d5a..8e65993 100644 --- a/test/commands/__init__.py +++ b/test/commands/__init__.py @@ -1,57 +1,15 @@ -import unittest from abc import ABC -from unittest.mock import Mock from meshtastic.protobuf.mesh_pb2 import MeshPacket -from src.bot import MeshtasticBot from src.commands.command import AbstractCommand, AbstractCommandWithSubcommands -from src.data_classes import MeshNode -from test.test_setup_data import get_test_bot, build_test_text_packet +from test import BaseFeatureTestCase +from test.test_setup_data import build_test_text_packet -class CommandTestCase(unittest.TestCase, ABC): +class CommandTestCase(BaseFeatureTestCase, ABC): command: AbstractCommand - bot: MeshtasticBot - mock_interface: Mock - test_admin_nodes: list[MeshNode] = [] - test_non_admin_nodes: list[MeshNode] = [] - test_nodes: list[MeshNode] = [] - - def setUp(self): - self.bot, self.test_non_admin_nodes, self.test_admin_nodes = get_test_bot() - self.test_nodes = self.test_non_admin_nodes + self.test_admin_nodes - self.mock_interface = self.bot.interface = Mock() - - def assert_message_sent(self, expected_response: str, to: MeshNode, want_ack: bool = False, multi_response=False): - - if multi_response: - self.mock_interface.sendText.assert_called() - - # Strip the newline character from the expected response - expected_response = expected_response.strip() - - # Assert that one of the calls matches the expected - for call_args in self.mock_interface.sendText.call_args_list: - if (call_args[1]['destinationId'] == to.user.id - and call_args[1]['wantAck'] == want_ack - and call_args[0][0].strip() == expected_response): - return - - self.fail( - f"Expected response with destinationId {to.user.id} and wantAck {want_ack}: \n" - f"{expected_response}\n" - f"\n" - f"not found in calls:\n" - f"{self.mock_interface.sendText.call_args_list}") - else: - self.mock_interface.sendText.assert_called_once_with( - expected_response, - destinationId=to.user.id, - wantAck=want_ack - ) - class CommandWSCTestCase(CommandTestCase): command: AbstractCommandWithSubcommands diff --git a/test/responders/__init__.py b/test/responders/__init__.py index 481ceb4..68e9f19 100644 --- a/test/responders/__init__.py +++ b/test/responders/__init__.py @@ -1,30 +1,8 @@ -import unittest from abc import ABC -from unittest.mock import Mock -from src.bot import MeshtasticBot -from src.data_classes import MeshNode from src.responders.responder import AbstractResponder -from test.test_setup_data import get_test_bot +from test import BaseFeatureTestCase -class ResponderTestCase(unittest.TestCase, ABC): +class ResponderTestCase(BaseFeatureTestCase, ABC): responder: AbstractResponder - - bot: MeshtasticBot - mock_interface: Mock - test_admin_nodes: list[MeshNode] = [] - test_non_admin_nodes: list[MeshNode] = [] - test_nodes: list[MeshNode] = [] - - def setUp(self): - self.bot, self.test_non_admin_nodes, self.test_admin_nodes = get_test_bot() - self.test_nodes = self.test_non_admin_nodes + self.test_admin_nodes - self.mock_interface = self.bot.interface = Mock() - - def assert_message_sent(self, expected_response: str, to: MeshNode, want_ack: bool = False): - self.mock_interface.sendText.assert_called_once_with( - expected_response, - destinationId=to.user.id, - wantAck=want_ack - ) From 580a379c6c4b43e856aa9477e226dc418bc3cd56 Mon Sep 17 00:00:00 2001 From: Patrick Skillen Date: Sun, 16 Mar 2025 13:07:45 +0000 Subject: [PATCH 3/3] tests: add tests for MessageReactionResponder --- src/base_feature.py | 4 +-- .../test_message_reaction_responder.py | 36 +++++++++++++++++++ test/test_base_feature.py | 4 +-- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/base_feature.py b/src/base_feature.py index b7b5939..28d1e2a 100644 --- a/src/base_feature.py +++ b/src/base_feature.py @@ -52,7 +52,7 @@ def react_in_channel(self, packet: MeshPacket, emoji: str) -> None: reply_id = packet['id'] channel = packet['channel'] if 'channel' in packet else 0 - self.bot.interface.sendReaction(emoji, reply_id, channelIndex=channel) + self.bot.interface.sendReaction(emoji, messageId=reply_id, channelIndex=channel) def react_in_dm(self, packet: MeshPacket, emoji: str) -> None: """ @@ -63,4 +63,4 @@ def react_in_dm(self, packet: MeshPacket, emoji: str) -> None: reply_id = packet['id'] sender = packet['fromId'] - self.bot.interface.sendReaction(emoji, reply_id, sender) + self.bot.interface.sendReaction(emoji, messageId=reply_id, destinationId=sender) diff --git a/test/responders/test_message_reaction_responder.py b/test/responders/test_message_reaction_responder.py index e69de29..fb30088 100644 --- a/test/responders/test_message_reaction_responder.py +++ b/test/responders/test_message_reaction_responder.py @@ -0,0 +1,36 @@ +import unittest +from unittest.mock import patch + +from src.responders.message_reaction_responder import MessageReactionResponder +from test.responders import ResponderTestCase +from test.test_setup_data import build_test_text_packet + + +class TestMessageReactionResponder(ResponderTestCase): + responder: MessageReactionResponder + + def setUp(self): + super().setUp() + self.responder = MessageReactionResponder(bot=self.bot, emoji="👍😊🎉") + + @patch('random.choice', return_value="👍") + def test_handle_packet(self, mock_random_choice): + sender_node = self.test_nodes[1] + + packet = build_test_text_packet('Hello', sender_node.user.id, self.bot.my_id, channel=1) + self.responder.handle_packet(packet) + + self.assert_reaction_sent("👍", packet['id'], channel=1) + + def test_handle_packet_not_enrolled(self): + sender_node = self.test_nodes[1] + + packet = build_test_text_packet('Hello', sender_node.user.id, self.bot.my_id, channel=1) + self.bot.user_prefs_persistence.get_user_prefs.return_value = None + self.responder.handle_packet(packet) + + self.bot.interface.sendReaction.assert_not_called() + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_base_feature.py b/test/test_base_feature.py index c11097d..c0910d9 100644 --- a/test/test_base_feature.py +++ b/test/test_base_feature.py @@ -43,13 +43,13 @@ def test_react_in_channel(self): sender = self.test_non_admin_nodes[1] packet = build_test_text_packet('!test', sender.user.id, self.bot.my_id, channel=1) self.feature.react_in_channel(packet, "👍") - self.mock_interface.sendReaction.assert_called_once_with("👍", packet['id'], channelIndex=1) + self.mock_interface.sendReaction.assert_called_once_with("👍", messageId=packet['id'], channelIndex=1) def test_react_in_dm(self): sender = self.test_non_admin_nodes[1] packet = build_test_text_packet('!test', sender.user.id, self.bot.my_id) self.feature.react_in_dm(packet, "👍") - self.mock_interface.sendReaction.assert_called_once_with("👍", packet['id'], sender.user.id) + self.mock_interface.sendReaction.assert_called_once_with("👍", messageId=packet['id'], destinationId=sender.user.id) if __name__ == '__main__':