mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-03-31 04:25:39 +00:00
Add ScheduleCommand (DM-only by default). Displays configured scheduled message times, target channels, message previews, and the current advertisement interval. Read-only; does not modify schedule state.
191 lines
6.5 KiB
Python
191 lines
6.5 KiB
Python
"""Tests for ScheduleCommand."""
|
|
|
|
from configparser import ConfigParser
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import pytest
|
|
|
|
from modules.commands.schedule_command import ScheduleCommand
|
|
from tests.conftest import mock_message
|
|
|
|
|
|
@pytest.fixture
|
|
def bot(mock_logger):
|
|
b = Mock()
|
|
b.logger = mock_logger
|
|
b.config = ConfigParser()
|
|
b.config.add_section("Bot")
|
|
b.config.set("Bot", "bot_name", "TestBot")
|
|
b.translator = Mock()
|
|
b.translator.translate = Mock(side_effect=lambda k, **kw: k)
|
|
b.translator.get_value = Mock(return_value=None)
|
|
b.command_manager = Mock()
|
|
b.command_manager.send_response = AsyncMock(return_value=True)
|
|
b.command_manager.monitor_channels = ["general"]
|
|
b.scheduler = Mock()
|
|
b.scheduler.scheduled_messages = {}
|
|
return b
|
|
|
|
|
|
def make_cmd(bot):
|
|
return ScheduleCommand(bot)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TestCanExecute
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCanExecute:
|
|
def test_dm_allowed_by_default(self, bot):
|
|
cmd = make_cmd(bot)
|
|
msg = mock_message(is_dm=True)
|
|
assert cmd.can_execute(msg) is True
|
|
|
|
def test_channel_blocked_when_dm_only(self, bot):
|
|
cmd = make_cmd(bot)
|
|
msg = mock_message(channel="general", is_dm=False)
|
|
assert cmd.can_execute(msg) is False
|
|
|
|
def test_channel_allowed_when_dm_only_false(self, bot):
|
|
bot.config.add_section("Schedule_Command")
|
|
bot.config.set("Schedule_Command", "dm_only", "false")
|
|
cmd = make_cmd(bot)
|
|
msg = mock_message(channel="general", is_dm=False)
|
|
assert cmd.can_execute(msg) is True
|
|
|
|
def test_disabled_blocks_all(self, bot):
|
|
bot.config.add_section("Schedule_Command")
|
|
bot.config.set("Schedule_Command", "enabled", "false")
|
|
cmd = make_cmd(bot)
|
|
msg = mock_message(is_dm=True)
|
|
assert cmd.can_execute(msg) is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TestBuildResponse — no scheduled messages
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestBuildResponseEmpty:
|
|
def test_no_schedules_says_none_configured(self, bot):
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "No scheduled messages configured" in response
|
|
|
|
def test_no_advert_interval_omits_advert_line(self, bot):
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "Advert" not in response
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TestBuildResponse — with scheduled messages
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestBuildResponseWithSchedules:
|
|
def test_shows_scheduled_count(self, bot):
|
|
bot.scheduler.scheduled_messages = {
|
|
"0900": ("general", "Good morning!"),
|
|
"1800": ("general", "Good evening!"),
|
|
}
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "Scheduled (2)" in response
|
|
|
|
def test_formats_time_as_hhmm(self, bot):
|
|
bot.scheduler.scheduled_messages = {"0930": ("general", "Hello")}
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "09:30" in response
|
|
|
|
def test_shows_channel_and_message(self, bot):
|
|
bot.scheduler.scheduled_messages = {"1200": ("alerts", "Noon check-in")}
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "#alerts" in response
|
|
assert "Noon check-in" in response
|
|
|
|
def test_long_message_is_truncated(self, bot):
|
|
long_msg = "A" * 100
|
|
bot.scheduler.scheduled_messages = {"0800": ("general", long_msg)}
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
# Preview should be ≤43 chars (40 + "...")
|
|
for line in response.splitlines():
|
|
if "08:00" in line:
|
|
# Extract the message part after the channel
|
|
parts = line.split(": ", 2)
|
|
if len(parts) == 3:
|
|
assert len(parts[2]) <= 43
|
|
|
|
def test_entries_sorted_by_time(self, bot):
|
|
bot.scheduler.scheduled_messages = {
|
|
"1800": ("general", "Evening"),
|
|
"0600": ("general", "Morning"),
|
|
"1200": ("general", "Noon"),
|
|
}
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
lines = [l for l in response.splitlines() if ":" in l and "#" in l]
|
|
times = [l.strip().split(" ")[0] for l in lines]
|
|
assert times == sorted(times)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TestAdvertInfo
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestAdvertInfo:
|
|
def test_advert_interval_shown(self, bot):
|
|
bot.config.set("Bot", "advert_interval_hours", "4")
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "Advert interval: every 4h" in response
|
|
|
|
def test_zero_interval_omitted(self, bot):
|
|
bot.config.set("Bot", "advert_interval_hours", "0")
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "Advert" not in response
|
|
|
|
def test_missing_advert_interval_omitted(self, bot):
|
|
# advert_interval_hours not in config → fallback 0 → omitted
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "Advert" not in response
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TestNoScheduler
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestNoScheduler:
|
|
def test_no_scheduler_attr_returns_gracefully(self, bot):
|
|
del bot.scheduler
|
|
cmd = make_cmd(bot)
|
|
response = cmd._build_response()
|
|
assert "No scheduled messages configured" in response
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TestExecute
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestExecute:
|
|
@pytest.mark.asyncio
|
|
async def test_execute_calls_send_response(self, bot):
|
|
cmd = make_cmd(bot)
|
|
msg = mock_message(is_dm=True)
|
|
result = await cmd.execute(msg)
|
|
assert result is True
|
|
bot.command_manager.send_response.assert_called_once()
|
|
call_args = bot.command_manager.send_response.call_args
|
|
response_text = call_args[0][1] # second positional arg is content
|
|
assert isinstance(response_text, str)
|
|
assert len(response_text) > 0
|