discordBot/tests/test_commands_pg.py
deadzilla 560dc558a9 refactor: убрал дублирование логики получения данных
- utils/pogoda.py: добавлена API_URL_WEATHER, format_weather_for_embed(),
  проверка None в format_weather_data_for_console()
- utils/morning_runner.py: вынесен MorningData (dataclass) и gather_morning();
  run_morning() использует их вместо ручного asyncio.gather
- utils/__init__.py: экспортирован публичный API (__all__)
- commands/pg.py: убран ручной парсинг погоды, используется
  format_weather_data_for_console()
- console_commands/morning.py: дубликат asyncio.gather заменён на gather_morning()
- console_commands/pogoda.py: хардкод URL заменён на API_URL_WEATHER
- console_commands/cat.py: заглушка заменена на рабочий вызов fetch_cat()
- tests/test_commands_pg.py: обновлён тест fetch_returns_none (бот теперь
  отправляет сообщение об ошибке вместо молчаливого возврата)
2026-06-09 17:37:18 +05:00

211 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from commands.pg import Pg
class TestPgInit:
"""Тесты инициализации Cog Pg."""
def test_init_sets_api_url(self):
"""__init__ должен устанавливать api_url."""
cog = Pg()
assert cog.api_url == "https://wttr.in/Magnitogorsk?format=j1&lang=ru"
class TestPgCommand:
"""Тесты команды !pogoda."""
def _make_cog(self):
return Pg()
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_pg_success(self):
"""Успешный запрос погоды должен отправить embed с данными."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data()
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.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_pg_fetch_returns_none(self):
"""fetch_weather вернул None — бот должен сообщить об ошибке."""
cog = self._make_cog()
ctx = self._make_ctx()
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=None)):
await cog.pg.callback(cog, ctx)
ctx.send.assert_called_once_with("Не удалось получить данные о погоде.")
@pytest.mark.asyncio
async def test_pg_empty_current_condition(self):
"""current_condition пустой список — код выбрасывает IndexError (баг в коде)."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = {"current_condition": []}
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
with pytest.raises(IndexError):
await cog.pg.callback(cog, ctx)
@pytest.mark.asyncio
async def test_pg_current_condition_none(self):
"""current_condition — пустой dict — бот должен сообщить об ошибке."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = {"current_condition": [{}]}
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.callback(cog, ctx)
ctx.send.assert_called_once_with("Не удалось получить данные о погоде.")
@pytest.mark.asyncio
async def test_pg_wind_non_numeric(self):
"""windspeedKmph — не число — wind должен быть ''."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(windspeedKmph="abc")
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.callback(cog, ctx)
args = ctx.send.call_args[0][0]
assert "Ветер: — м/с" in args
@pytest.mark.asyncio
async def test_pg_wind_none(self):
"""windspeedKmph отсутствует — wind должен быть ''."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(windspeedKmph=None)
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.callback(cog, ctx)
args = ctx.send.call_args[0][0]
assert "Ветер: — м/с" in args
@pytest.mark.asyncio
async def test_pg_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.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.callback(cog, ctx)
args = ctx.send.call_args[0][0]
assert "Ветер: 0.0 м/с" in args
@pytest.mark.asyncio
async def test_pg_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.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.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_pg_translate_unknown_weather(self):
"""Неизвестное описание погоды должно возвращать оригинал."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(weatherDesc=[{"value": "UnknownXYZ"}])
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.callback(cog, ctx)
args = ctx.send.call_args[0][0]
assert "Описание: UnknownXYZ" in args
@pytest.mark.asyncio
async def test_pg_russian_weather_description(self):
"""Описание погоды на русском должно корректно переводиться."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(weatherDesc=[{"value": "Переменная облачность"}])
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.callback(cog, ctx)
args = ctx.send.call_args[0][0]
assert "Описание: Переменная облачность" in args
@pytest.mark.asyncio
async def test_pg_negative_pressure(self):
"""Отрицательное давление должно конвертироваться."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(pressure="-50")
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.callback(cog, ctx)
args = ctx.send.call_args[0][0]
assert "Давление: -37.5 мм рт. ст." in args
@pytest.mark.asyncio
async def test_pg_high_wind(self):
"""Большая скорость ветра должна корректно округляться."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(windspeedKmph="123")
with patch("commands.pg.fetch_weather", new=AsyncMock(return_value=weather)):
await cog.pg.callback(cog, ctx)
args = ctx.send.call_args[0][0]
assert "Ветер: 34.2 м/с" in args