Files
meshcore-bot/tests/commands/test_hello_command.py
T
Piotr Synowiec 258c9680b2 feat(hello): optionally address the sender in the reply (#292)
Adds a `[Hello_Command] include_sender` setting (default off) that names the user the hello reply is answering, so a busy channel can tell whose greeting the bot picked up.

The mention takes the place of the random human descriptor, keeping the translated sentence intact, and is dropped when it would push the reply past the channel body budget. DMs are unaffected, and channel sender names are sanitized before being echoed.
2026-09-19 21:28:19 -07:00

171 lines
8.5 KiB
Python

"""Tests for modules.commands.hello_command."""
from unittest.mock import patch
import pytest
from modules.commands.hello_command import HelloCommand
from tests.conftest import mock_message
class TestHelloCommand:
"""Tests for HelloCommand."""
def test_is_emoji_only_message_vulcan_salute(self, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
cmd = HelloCommand(command_mock_bot)
assert cmd.is_emoji_only_message("🖖") is True
def test_is_emoji_only_message_with_whitespace(self, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
cmd = HelloCommand(command_mock_bot)
assert cmd.is_emoji_only_message(" 🖖 ") is True
def test_is_emoji_only_message_text_returns_false(self, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
cmd = HelloCommand(command_mock_bot)
assert cmd.is_emoji_only_message("hello") is False
def test_is_emoji_only_message_empty_returns_false(self, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
cmd = HelloCommand(command_mock_bot)
assert cmd.is_emoji_only_message("") is False
@patch("datetime.datetime")
def test_get_random_greeting_deterministic_with_mocked_time(self, mock_datetime, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
mock_now = mock_datetime.now.return_value
mock_now.hour = 10 # morning
cmd = HelloCommand(command_mock_bot)
with patch("modules.commands.hello_command.random.choice", side_effect=lambda x: x[0]):
result = cmd.get_random_greeting()
assert isinstance(result, str) and len(result) > 0
def test_get_emoji_response_vulcan(self, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
cmd = HelloCommand(command_mock_bot)
with patch("modules.commands.hello_command.random.choice", side_effect=lambda x: x[0]):
result = cmd.get_emoji_response("🖖", "TestBot")
assert "🖖" in result or "TestBot" in result
def test_can_execute_when_enabled(self, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
cmd = HelloCommand(command_mock_bot)
msg = mock_message(content="hello", is_dm=True)
assert cmd.can_execute(msg) is True
def test_can_execute_when_disabled(self, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "false")
cmd = HelloCommand(command_mock_bot)
msg = mock_message(content="hello", is_dm=True)
assert cmd.can_execute(msg) is False
@pytest.mark.asyncio
async def test_execute_text_greeting_sends_response(self, command_mock_bot):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
cmd = HelloCommand(command_mock_bot)
msg = mock_message(content="hello", is_dm=True)
with patch("modules.commands.hello_command.random.choice", side_effect=lambda x: x[0]):
result = await cmd.execute(msg)
assert result is True
call_args = command_mock_bot.command_manager.send_response.call_args
assert call_args is not None
response = call_args[0][1]
assert isinstance(response, str) and len(response) > 0
class TestHelloCommandIncludeSender:
"""Tests for the Hello_Command include_sender setting."""
@staticmethod
def _command(command_mock_bot, include_sender):
command_mock_bot.config.add_section("Hello_Command")
command_mock_bot.config.set("Hello_Command", "enabled", "true")
command_mock_bot.config.set("Hello_Command", "include_sender", str(include_sender).lower())
return HelloCommand(command_mock_bot)
def test_mention_empty_when_setting_off(self, command_mock_bot):
cmd = self._command(command_mock_bot, False)
assert cmd.get_sender_mention(mock_message(sender_id="Bob")) == ""
def test_mention_built_for_channel_sender(self, command_mock_bot):
cmd = self._command(command_mock_bot, True)
assert cmd.get_sender_mention(mock_message(sender_id="Bob")) == "@[Bob]"
def test_mention_empty_for_dm(self, command_mock_bot):
"""A DM has one recipient; naming them spends airtime to say nothing."""
cmd = self._command(command_mock_bot, True)
assert cmd.get_sender_mention(mock_message(sender_id="Bob", is_dm=True)) == ""
def test_mention_empty_for_unnamed_channel_sender(self, command_mock_bot):
"""message_handler uses this literal when the packet has no name prefix."""
cmd = self._command(command_mock_bot, True)
assert cmd.get_sender_mention(mock_message(sender_id="Channel User")) == ""
def test_mention_empty_when_sender_id_missing(self, command_mock_bot):
cmd = self._command(command_mock_bot, True)
assert cmd.get_sender_mention(mock_message(sender_id=None)) == ""
def test_mention_strips_brackets_that_would_forge_a_second_mention(self, command_mock_bot):
"""Channel names come off the wire unsanitized (handle_channel_message)."""
cmd = self._command(command_mock_bot, True)
mention = cmd.get_sender_mention(mock_message(sender_id="Bob] hi @[Alice"))
assert mention == "@[Bob hi @Alice]"
assert mention.count("[") == 1 and mention.count("]") == 1
def test_mention_strips_control_characters(self, command_mock_bot):
cmd = self._command(command_mock_bot, True)
assert cmd.get_sender_mention(mock_message(sender_id="Bo\nb\x00")) == "@[Bob]"
def test_mention_strips_braces_that_would_break_formatting(self, command_mock_bot):
"""The mention is embedded before .format(bot_name=...) runs."""
cmd = self._command(command_mock_bot, True)
assert cmd.get_sender_mention(mock_message(sender_id="Bo{b}")) == "@[Bob]"
def test_greeting_uses_mention_in_place_of_descriptor(self, command_mock_bot):
cmd = self._command(command_mock_bot, True)
with patch("modules.commands.hello_command.random.choice", side_effect=lambda x: x[0]):
with_mention = cmd.get_random_greeting("@[Bob]")
without = cmd.get_random_greeting()
assert "@[Bob]" in with_mention
# The name takes the descriptor's slot rather than riding in front of it.
assert not with_mention.startswith("@[Bob]")
assert with_mention.split()[0] == without.split()[0]
def test_emoji_response_prefixes_mention(self, command_mock_bot):
"""Canned emoji lines have no descriptor slot, so the mention leads."""
cmd = self._command(command_mock_bot, True)
with patch("modules.commands.hello_command.random.choice", side_effect=lambda x: x[0]):
result = cmd.get_emoji_response("🖖", "TestBot", "@[Bob]")
assert result.startswith("@[Bob] ")
@pytest.mark.asyncio
async def test_execute_names_channel_sender(self, command_mock_bot):
cmd = self._command(command_mock_bot, True)
msg = mock_message(content="hello", sender_id="Bob")
with patch("modules.commands.hello_command.random.choice", side_effect=lambda x: x[0]):
assert await cmd.execute(msg) is True
response = command_mock_bot.command_manager.send_response.call_args[0][1]
assert "@[Bob]" in response
@pytest.mark.asyncio
async def test_execute_drops_mention_that_would_overflow_the_budget(self, command_mock_bot):
"""Nothing downstream truncates, so an over-budget reply is cut on air."""
cmd = self._command(command_mock_bot, True)
msg = mock_message(content="hello", sender_id="B" * 60)
with patch.object(cmd, "get_max_message_length", return_value=60):
with patch("modules.commands.hello_command.random.choice", side_effect=lambda x: x[0]):
assert await cmd.execute(msg) is True
response = command_mock_bot.command_manager.send_response.call_args[0][1]
assert "@[" not in response
assert len(response.encode("utf-8")) <= 60