from __future__ import annotations import importlib from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock import pytest from aiogram.exceptions import TelegramBadRequest @pytest.fixture(autouse=True) def fresh_db(tmp_path, monkeypatch): monkeypatch.setenv("DB_PATH", str(tmp_path / "test.db")) import adapter.telegram.db as db_mod importlib.reload(db_mod) db_mod.init_db() return db_mod def make_message(*, user_id=1, thread_id=42, chat_id=100, text="/new"): m = SimpleNamespace() m.from_user = SimpleNamespace(id=user_id, full_name="Alice") m.message_thread_id = thread_id m.chat = SimpleNamespace(id=chat_id) m.text = text m.answer = AsyncMock() m.reply = AsyncMock() m.bot = MagicMock() m.bot.create_forum_topic = AsyncMock( return_value=SimpleNamespace(message_thread_id=200) ) m.bot.close_forum_topic = AsyncMock() m.bot.delete_forum_topic = AsyncMock() m.bot.edit_forum_topic = AsyncMock() m.bot.send_message = AsyncMock() return m async def test_cmd_new_creates_topic(fresh_db, monkeypatch): import adapter.telegram.handlers.commands as mod importlib.reload(mod) fresh_db.create_chat(1, 42, "Чат #1") msg = make_message(user_id=1, thread_id=42, chat_id=100) await mod.cmd_new(msg) msg.bot.create_forum_topic.assert_called_once() call_kwargs = str(msg.bot.create_forum_topic.call_args) assert "Чат #2" in call_kwargs assert fresh_db.get_chat(1, 200) is not None async def test_cmd_archive_deletes_topic_when_possible(fresh_db, monkeypatch): """When delete_forum_topic succeeds (user-created topic), no answer is sent.""" import adapter.telegram.handlers.commands as mod importlib.reload(mod) fresh_db.create_chat(1, 42, "Чат #1") msg = make_message(user_id=1, thread_id=42, chat_id=100) await mod.cmd_archive(msg) msg.bot.delete_forum_topic.assert_called_once_with(chat_id=100, message_thread_id=42) assert fresh_db.get_chat(1, 42)["archived_at"] is not None msg.answer.assert_not_called() async def test_cmd_archive_fallback_message_when_delete_fails(fresh_db, monkeypatch): """When delete_forum_topic fails (bot-created topic), user gets explanation.""" import adapter.telegram.handlers.commands as mod importlib.reload(mod) fresh_db.create_chat(1, 42, "Чат #1") msg = make_message(user_id=1, thread_id=42, chat_id=100) msg.bot.delete_forum_topic = AsyncMock( side_effect=TelegramBadRequest(method=MagicMock(), message="not a supergroup forum") ) await mod.cmd_archive(msg) assert fresh_db.get_chat(1, 42)["archived_at"] is not None msg.answer.assert_called_once() assert "архивирован" in msg.answer.call_args[0][0] async def test_cmd_archive_unknown_topic_replies_error(fresh_db, monkeypatch): import adapter.telegram.handlers.commands as mod importlib.reload(mod) msg = make_message(user_id=1, thread_id=999, chat_id=100) await mod.cmd_archive(msg) msg.answer.assert_called_once() async def test_cmd_rename_updates_db_and_topic(fresh_db, monkeypatch): import adapter.telegram.handlers.commands as mod importlib.reload(mod) fresh_db.create_chat(1, 42, "Чат #1") msg = make_message(user_id=1, thread_id=42, chat_id=100, text="/rename Работа") await mod.cmd_rename(msg) msg.bot.edit_forum_topic.assert_called_once_with( chat_id=100, message_thread_id=42, name="Работа" ) assert fresh_db.get_chat(1, 42)["chat_name"] == "Работа" async def test_cmd_new_topics_limit(fresh_db): """When Telegram returns topics limit error, user gets a friendly message.""" import adapter.telegram.handlers.commands as mod importlib.reload(mod) msg = make_message(user_id=1, thread_id=42, chat_id=100) msg.bot.create_forum_topic = AsyncMock( side_effect=TelegramBadRequest(method=MagicMock(), message="topics limit exceeded") ) await mod.cmd_new(msg) msg.answer.assert_called_once() assert "лимит" in msg.answer.call_args[0][0] # No chat should be created assert fresh_db.count_active_chats(1) == 0 async def test_cmd_archive_general_topic(fresh_db): """/archive in General topic (thread_id=None) replies with 'not found'.""" import adapter.telegram.handlers.commands as mod importlib.reload(mod) msg = make_message(user_id=1, thread_id=None, chat_id=100) await mod.cmd_archive(msg) msg.answer.assert_called_once() msg.bot.close_forum_topic.assert_not_called()