"""Tests for duty_teller.handlers.group_duty_pin (sync wrappers, update_group_pin, pin_duty_cmd).""" from unittest.mock import AsyncMock, MagicMock, patch import pytest from duty_teller.handlers import group_duty_pin as mod class TestSyncWrappers: """Tests for _get_duty_message_text_sync, _sync_save_pin, _sync_delete_pin, _sync_get_message_id, _get_all_pin_chat_ids_sync.""" def test_get_duty_message_text_sync(self): with patch.object(mod, "session_scope") as mock_scope: mock_session = MagicMock() mock_scope.return_value.__enter__.return_value = mock_session mock_scope.return_value.__exit__.return_value = None with patch.object(mod, "get_duty_message_text", return_value="Duty text"): result = mod._get_duty_message_text_sync("en") assert result == "Duty text" mock_scope.assert_called_once() def test_sync_save_pin(self): with patch.object(mod, "session_scope") as mock_scope: mock_session = MagicMock() mock_scope.return_value.__enter__.return_value = mock_session mock_scope.return_value.__exit__.return_value = None with patch.object(mod, "save_pin") as mock_save: mod._sync_save_pin(100, 42) mock_save.assert_called_once_with(mock_session, 100, 42) def test_sync_delete_pin(self): with patch.object(mod, "session_scope") as mock_scope: mock_session = MagicMock() mock_scope.return_value.__enter__.return_value = mock_session mock_scope.return_value.__exit__.return_value = None with patch.object(mod, "delete_pin") as mock_delete: mod._sync_delete_pin(200) mock_delete.assert_called_once_with(mock_session, 200) def test_sync_get_message_id(self): with patch.object(mod, "session_scope") as mock_scope: mock_session = MagicMock() mock_scope.return_value.__enter__.return_value = mock_session mock_scope.return_value.__exit__.return_value = None with patch.object(mod, "get_message_id", return_value=99): result = mod._sync_get_message_id(300) assert result == 99 def test_get_all_pin_chat_ids_sync(self): with patch.object(mod, "session_scope") as mock_scope: mock_session = MagicMock() mock_scope.return_value.__enter__.return_value = mock_session mock_scope.return_value.__exit__.return_value = None with patch.object(mod, "get_all_pin_chat_ids", return_value=[10, 20]): result = mod._get_all_pin_chat_ids_sync() assert result == [10, 20] @pytest.mark.asyncio async def test_update_group_pin_edits_message_and_schedules_next(): """update_group_pin: with message_id and text, edits message and schedules next update.""" context = MagicMock() context.job = MagicMock() context.job.data = {"chat_id": 123} context.bot = MagicMock() context.bot.edit_message_text = 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.object(mod, "_sync_get_message_id", return_value=1): with patch.object( mod, "_get_duty_message_text_sync", return_value="Current duty" ): with patch.object(mod, "_get_next_shift_end_sync", return_value=None): with patch.object(mod, "_schedule_next_update", AsyncMock()): await mod.update_group_pin(context) context.bot.edit_message_text.assert_called_once_with( chat_id=123, message_id=1, text="Current duty" ) @pytest.mark.asyncio async def test_update_group_pin_no_message_id_skips(): """update_group_pin: when no pin record (message_id None), does not edit.""" context = MagicMock() context.job = MagicMock() context.job.data = {"chat_id": 456} context.bot = MagicMock() context.bot.edit_message_text = AsyncMock() with patch.object(mod, "_sync_get_message_id", return_value=None): await mod.update_group_pin(context) context.bot.edit_message_text.assert_not_called() @pytest.mark.asyncio async def test_pin_duty_cmd_group_only_reply(): """pin_duty_cmd in private chat -> reply group_only.""" update = MagicMock() update.message = MagicMock() update.message.reply_text = AsyncMock() update.effective_chat = MagicMock() update.effective_chat.type = "private" update.effective_chat.id = 1 update.effective_user = MagicMock() context = MagicMock() with patch("duty_teller.handlers.group_duty_pin.get_lang", return_value="en"): with patch("duty_teller.handlers.group_duty_pin.t") as mock_t: mock_t.return_value = "Group only" await mod.pin_duty_cmd(update, context) update.message.reply_text.assert_called_once_with("Group only") mock_t.assert_called_with("en", "pin_duty.group_only") @pytest.mark.asyncio async def test_pin_duty_cmd_group_pins_and_replies_pinned(): """pin_duty_cmd in group with existing pin record -> pin and reply pinned.""" 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.pin_chat_message = AsyncMock() with patch("duty_teller.handlers.group_duty_pin.get_lang", return_value="en"): with patch.object(mod, "_sync_get_message_id", return_value=5): 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.pin_chat_message.assert_called_once_with( chat_id=100, message_id=5, disable_notification=True ) update.message.reply_text.assert_called_once_with("Pinned")