Files
meshcore-bot/tests/test_schedule_command.py
Stacy Olivas 97e5c59ca6 feat: !schedule command listing scheduled messages and advert interval
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.
2026-03-17 18:07:18 -07:00

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