152 lines
5.1 KiB
Python
152 lines
5.1 KiB
Python
"""Тесты для 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 + "..."
|