- pytest.ini для конфигурации тестов - tests/test_pogoda.py — тесты translate_weather, pressure_to_mmhg, wmo_to_russian (93 теста) - tests/test_fetch_cat.py — тесты fetch_cat (10 тестов) - tests/test_fetch_rss.py — тесты fetch_rss (20 тестов) - tests/test_format_articles.py — тесты truncate_title, parse_date, format_articles (24 теста) - tests/test_fetch_weather.py — тесты fetch_weather, fetch_open_meteo (20 тестов) - tests/test_commands_pogoda.py — тесты команды !pogoda (13 тестов) - Обновить AGENTS.md и requirements.txt
211 lines
8.2 KiB
Python
211 lines
8.2 KiB
Python
import pytest
|
||
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
||
from commands.pogoda import Pogoda
|
||
|
||
|
||
class TestPogodaInit:
|
||
"""Тесты инициализации Cog Pogoda."""
|
||
|
||
def test_init_sets_api_url(self):
|
||
"""__init__ должен устанавливать api_url."""
|
||
cog = Pogoda()
|
||
assert cog.api_url == "https://wttr.in/Magnitogorsk?format=j1&lang=ru"
|
||
|
||
|
||
class TestPogodaCommand:
|
||
"""Тесты команды !pogoda."""
|
||
|
||
def _make_cog(self):
|
||
return Pogoda()
|
||
|
||
def _make_ctx(self, send_return=None):
|
||
ctx = MagicMock()
|
||
ctx.send = AsyncMock(return_value=send_return)
|
||
return ctx
|
||
|
||
def _make_weather_data(self, **extra):
|
||
"""Создать mock weather data с дефолтными полями."""
|
||
defaults = {
|
||
"current_condition": [
|
||
{
|
||
"temp_C": "22",
|
||
"FeelsLikeC": "24",
|
||
"weatherDesc": [{"value": "Clear"}],
|
||
"humidity": "45",
|
||
"windspeedKmph": "18",
|
||
"pressure": "1013",
|
||
}
|
||
]
|
||
}
|
||
defaults["current_condition"][0].update(extra)
|
||
return defaults
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_success(self):
|
||
"""Успешный запрос погоды должен отправить embed с данными."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data()
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
ctx.send.assert_called_once()
|
||
args = ctx.send.call_args[0][0]
|
||
assert "Температура: 22°C" in args
|
||
assert "(ощущается как 24°C)" in args
|
||
assert "Описание: Ясно" in args
|
||
assert "Влажность: 45%" in args
|
||
assert "Ветер: 5.0 м/с" in args
|
||
assert "Давление: 759.8 мм рт. ст." in args
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_fetch_returns_none(self):
|
||
"""fetch_weather вернул None — бот должен ничего не отправить."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=None)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
ctx.send.assert_not_called()
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_empty_current_condition(self):
|
||
"""current_condition пустой список — код выбрасывает IndexError (баг в коде)."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = {"current_condition": []}
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
with pytest.raises(IndexError):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_current_condition_none(self):
|
||
"""current_condition — пустой dict — бот должен сообщить об ошибке."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = {"current_condition": [{}]}
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
ctx.send.assert_called_once_with("Не удалось получить данные о погоде.")
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_wind_non_numeric(self):
|
||
"""windspeedKmph — не число — wind должен быть '—'."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data(windspeedKmph="abc")
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
args = ctx.send.call_args[0][0]
|
||
assert "Ветер: — м/с" in args
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_wind_none(self):
|
||
"""windspeedKmph отсутствует — wind должен быть '—'."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data(windspeedKmph=None)
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
args = ctx.send.call_args[0][0]
|
||
assert "Ветер: — м/с" in args
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_zero_wind(self):
|
||
"""windspeedKmph = 0 — wind должен быть 0.0."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data(windspeedKmph="0")
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
args = ctx.send.call_args[0][0]
|
||
assert "Ветер: 0.0 м/с" in args
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_default_values(self):
|
||
"""Поля с отсутствующими значениями должны давать '—'."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data(
|
||
temp_C=None,
|
||
FeelsLikeC=None,
|
||
weatherDesc=[{"value": None}],
|
||
humidity=None,
|
||
pressure=None,
|
||
)
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
args = ctx.send.call_args[0][0]
|
||
# dict.get(key, default) возвращает None, если ключ есть, но значение None
|
||
assert "Температура: None°C" in args
|
||
assert "ощущается как None°C" in args
|
||
assert "Описание: —" in args
|
||
assert "Влажность: None%" in args
|
||
assert "Давление: — мм рт. ст." in args
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_translate_unknown_weather(self):
|
||
"""Неизвестное описание погоды должно возвращать оригинал."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data(weatherDesc=[{"value": "UnknownXYZ"}])
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
args = ctx.send.call_args[0][0]
|
||
assert "Описание: UnknownXYZ" in args
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_russian_weather_description(self):
|
||
"""Описание погоды на русском должно корректно переводиться."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data(weatherDesc=[{"value": "Переменная облачность"}])
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
args = ctx.send.call_args[0][0]
|
||
assert "Описание: Переменная облачность" in args
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_negative_pressure(self):
|
||
"""Отрицательное давление должно конвертироваться."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data(pressure="-50")
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
args = ctx.send.call_args[0][0]
|
||
assert "Давление: -37.5 мм рт. ст." in args
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_pogoda_high_wind(self):
|
||
"""Большая скорость ветра должна корректно округляться."""
|
||
cog = self._make_cog()
|
||
ctx = self._make_ctx()
|
||
weather = self._make_weather_data(windspeedKmph="123")
|
||
|
||
with patch("commands.pogoda.fetch_weather", new=AsyncMock(return_value=weather)):
|
||
await cog.pogoda.callback(cog, ctx)
|
||
|
||
args = ctx.send.call_args[0][0]
|
||
assert "Ветер: 34.2 м/с" in args
|