"""Тесты для commands/news.py — команда !nw.""" import sys from pathlib import Path from unittest.mock import AsyncMock, MagicMock, patch import pytest ROOT_DIR = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT_DIR)) def make_context() -> MagicMock: """Создать мокированный ctx.""" ctx = MagicMock() ctx.author.name = "TestUser" ctx.send = AsyncMock(return_value=None) return ctx class TestNewsInit: """Тесты инициализации News cog.""" def test_news_cog_instantiates(self) -> None: """News должен создаваться без параметров.""" from commands.news import News cog = News() assert cog is not None def test_news_has_command(self) -> None: """News должен содержать команду nw.""" from commands.news import News cog = News() assert cog.nw.name == "nw" class TestNewsCommand: """Тесты команды !nw.""" def _make_articles(self, count: int = 3) -> list[dict]: return [ {"title": f"Статья {i}", "link": f"https://habr.com/article/{i}", "pub_date": f"Mon, 28 May 2026 10:00:00 +0000", "creator": "author", "tags": []} for i in range(1, count + 1) ] @pytest.mark.asyncio async def test_nw_success_articles_and_posts(self) -> None: """Успешный ответ API для статей и постов.""" from commands.news import News cog = News() ctx = make_context() articles = self._make_articles(3) posts = self._make_articles(2) with patch("commands.news.fetch_rss", new_callable=AsyncMock) as mock_rss: mock_rss.side_effect = [articles, posts] await cog.nw(cog, ctx) ctx.send.assert_awaited_once() call_count = mock_rss.await_count assert call_count == 2 # статьи + посты @pytest.mark.asyncio async def test_nw_articles_none(self) -> None: """fetch_rss вернул None для статей -> fallback сообщение.""" from commands.news import News cog = News() ctx = make_context() with patch("commands.news.fetch_rss", new_callable=AsyncMock) as mock_rss: mock_rss.return_value = None await cog.nw(cog, ctx) ctx.send.assert_awaited_once() content = ctx.send.call_args[0][0] assert "Не удалось получить новости" in content @pytest.mark.asyncio async def test_nw_empty_articles(self) -> None: """Пустой список статей -> 'Новостей пока нет'.""" from commands.news import News cog = News() ctx = make_context() with patch("commands.news.fetch_rss", new_callable=AsyncMock) as mock_rss: mock_rss.return_value = [] await cog.nw(cog, ctx) ctx.send.assert_awaited_once() content = ctx.send.call_args[0][0] assert "Новостей пока нет" in content @pytest.mark.asyncio async def test_nw_posts_none(self) -> None: """fetch_rss вернул None для постов -> fallback в сообщение.""" from commands.news import News cog = News() ctx = make_context() articles = self._make_articles(2) with patch("commands.news.fetch_rss", new_callable=AsyncMock) as mock_rss: mock_rss.side_effect = [articles, None] await cog.nw(cog, ctx) ctx.send.assert_awaited_once() content = ctx.send.call_args[0][0] assert "Не удалось получить новости" in content @pytest.mark.asyncio async def test_nw_posts_empty(self) -> None: """Пустой список постов -> 'Новостей пока нет' для постов.""" from commands.news import News cog = News() ctx = make_context() articles = self._make_articles(2) with patch("commands.news.fetch_rss", new_callable=AsyncMock) as mock_rss: mock_rss.side_effect = [articles, []] await cog.nw(cog, ctx) ctx.send.assert_awaited_once() content = ctx.send.call_args[0][0] # Second "Новостей пока нет" for posts section count = content.count("Новостей пока нет") assert count >= 1 @pytest.mark.asyncio async def test_nw_limits_articles_to_5(self) -> None: """Более 5 статей -> truncate_message обрежет.""" from commands.news import News cog = News() ctx = make_context() articles = self._make_articles(20) posts = self._make_articles(20) with patch("commands.news.fetch_rss", new_callable=AsyncMock) as mock_rss: mock_rss.side_effect = [articles, posts] await cog.nw(cog, ctx) ctx.send.assert_awaited_once() content = ctx.send.call_args[0][0] # truncate_message limits to 2000 chars for plain text assert len(content) <= 2003 # 2000 + "..."