feat: enhance group duty pin command functionality
All checks were successful
CI / lint-and-test (push) Successful in 25s
Docker Build and Release / build-and-push (push) Successful in 56s
Docker Build and Release / release (push) Successful in 9s

- Updated the `pin_duty_cmd` to handle cases where no message ID is found by sending a new duty message, pinning it, saving the pin, and scheduling the next update.
- Improved error handling for message sending and pinning operations, providing appropriate replies based on success or failure.
- Enhanced unit tests to cover the new behavior, ensuring proper functionality and error handling in various scenarios.
This commit is contained in:
2026-02-25 14:43:19 +03:00
parent 3c3a2c507c
commit 8a80af32d8
8 changed files with 286 additions and 9 deletions

View File

@@ -306,8 +306,8 @@ async def test_pin_duty_cmd_group_pins_and_replies_pinned():
@pytest.mark.asyncio
async def test_pin_duty_cmd_no_message_id_replies_no_message():
"""pin_duty_cmd: no pin record (_sync_get_message_id -> None) -> reply pin_duty.no_message."""
async def test_pin_duty_cmd_no_message_id_creates_sends_pins_saves_schedules_replies_pinned():
"""pin_duty_cmd: no pin record -> send_message, pin, save_pin, schedule, reply pinned."""
update = MagicMock()
update.message = MagicMock()
update.message.reply_text = AsyncMock()
@@ -316,14 +316,97 @@ async def test_pin_duty_cmd_no_message_id_replies_no_message():
update.effective_chat.id = 100
update.effective_user = MagicMock()
context = MagicMock()
context.bot = MagicMock()
new_msg = MagicMock()
new_msg.message_id = 42
context.bot.send_message = AsyncMock(return_value=new_msg)
context.bot.pin_chat_message = AsyncMock()
context.application = MagicMock()
context.application.job_queue = MagicMock()
context.application.job_queue.get_jobs_by_name = MagicMock(return_value=[])
context.application.job_queue.run_once = MagicMock()
with patch("duty_teller.handlers.group_duty_pin.get_lang", return_value="en"):
with patch.object(mod, "_sync_get_message_id", return_value=None):
with patch("duty_teller.handlers.group_duty_pin.t") as mock_t:
mock_t.return_value = "No message to pin"
await mod.pin_duty_cmd(update, context)
update.message.reply_text.assert_called_once_with("No message to pin")
mock_t.assert_called_with("en", "pin_duty.no_message")
with patch.object(mod, "_get_duty_message_text_sync", return_value="Duty text"):
with patch.object(mod, "_sync_save_pin") as mock_save:
with patch.object(mod, "_get_next_shift_end_sync", return_value=None):
with patch.object(mod, "_schedule_next_update", AsyncMock()):
with patch("duty_teller.handlers.group_duty_pin.t") as mock_t:
mock_t.return_value = "Pinned"
await mod.pin_duty_cmd(update, context)
context.bot.send_message.assert_called_once_with(chat_id=100, text="Duty text")
context.bot.pin_chat_message.assert_called_once_with(
chat_id=100, message_id=42, disable_notification=True
)
mock_save.assert_called_once_with(100, 42)
update.message.reply_text.assert_called_once_with("Pinned")
mock_t.assert_called_with("en", "pin_duty.pinned")
@pytest.mark.asyncio
async def test_pin_duty_cmd_no_message_id_send_message_raises_replies_failed():
"""pin_duty_cmd: no pin record, send_message raises BadRequest -> reply pin_duty.failed."""
update = MagicMock()
update.message = MagicMock()
update.message.reply_text = AsyncMock()
update.effective_chat = MagicMock()
update.effective_chat.type = "group"
update.effective_chat.id = 100
update.effective_user = MagicMock()
context = MagicMock()
context.bot = MagicMock()
context.bot.send_message = AsyncMock(side_effect=BadRequest("Chat not found"))
context.application = MagicMock()
with patch("duty_teller.handlers.group_duty_pin.get_lang", return_value="en"):
with patch.object(mod, "_sync_get_message_id", return_value=None):
with patch.object(mod, "_get_duty_message_text_sync", return_value="Duty"):
with patch.object(mod, "_sync_save_pin") as mock_save:
with patch.object(mod, "_schedule_next_update", AsyncMock()) as mock_schedule:
with patch("duty_teller.handlers.group_duty_pin.t") as mock_t:
mock_t.return_value = "Failed"
await mod.pin_duty_cmd(update, context)
update.message.reply_text.assert_called_once_with("Failed")
mock_t.assert_called_with("en", "pin_duty.failed")
mock_save.assert_not_called()
mock_schedule.assert_not_called()
@pytest.mark.asyncio
async def test_pin_duty_cmd_no_message_id_pin_raises_saves_and_replies_could_not_pin():
"""pin_duty_cmd: no pin record, pin_chat_message raises -> save pin, reply could_not_pin_make_admin."""
update = MagicMock()
update.message = MagicMock()
update.message.reply_text = AsyncMock()
update.effective_chat = MagicMock()
update.effective_chat.type = "group"
update.effective_chat.id = 100
update.effective_user = MagicMock()
context = MagicMock()
context.bot = MagicMock()
new_msg = MagicMock()
new_msg.message_id = 43
context.bot.send_message = AsyncMock(return_value=new_msg)
context.bot.pin_chat_message = AsyncMock(side_effect=Forbidden("Not enough rights"))
context.application = MagicMock()
context.application.job_queue = MagicMock()
context.application.job_queue.get_jobs_by_name = MagicMock(return_value=[])
context.application.job_queue.run_once = MagicMock()
with patch("duty_teller.handlers.group_duty_pin.get_lang", return_value="en"):
with patch.object(mod, "_sync_get_message_id", return_value=None):
with patch.object(mod, "_get_duty_message_text_sync", return_value="Duty"):
with patch.object(mod, "_sync_save_pin") as mock_save:
with patch.object(mod, "_get_next_shift_end_sync", return_value=None):
with patch.object(mod, "_schedule_next_update", AsyncMock()):
with patch("duty_teller.handlers.group_duty_pin.t") as mock_t:
mock_t.return_value = "Make me admin to pin"
await mod.pin_duty_cmd(update, context)
context.bot.send_message.assert_called_once_with(chat_id=100, text="Duty")
mock_save.assert_called_once_with(100, 43)
update.message.reply_text.assert_called_once_with("Make me admin to pin")
mock_t.assert_called_with("en", "pin_duty.could_not_pin_make_admin")
@pytest.mark.asyncio