discordBot/tests/test_commands_pg.py
deadzilla 48e64c2bc8 Replace wttr.in/Open-Meteo with Yandex Weather API
- Switch from wttr.in + Open-Meteo fallback to Yandex Weather API
- Add YANDEX_WEATHER_API_KEY to .env and config validation
- Enrich weather output: wind gusts, wind direction (Russian)
- Pressure now in mmHg directly from Yandex (no conversion needed)
- Update rate limiter: weather_limiter + open_meteo_limiter → yandex_weather_limiter
- Remove dead code: API_URL_WEATHER, fetch_open_meteo, translate_weather
- Update all tests (271 passed)
2026-07-20 23:31:11 +05:00

241 lines
9.0 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_cog_initialized(self) -> None:
"""Cog инициализируется без ошибок."""
cog = Pg()
assert cog is not None
class TestPgCommand:
"""Тесты команды !pg."""
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 (Яндекс Погода формат).
windspeedKmph — м/с от Яндекса.
wind_gust — порывы ветра в м/с.
wind_dir — направление ветра.
pressure — уже в мм рт. ст.
weatherDesc — на русском (из yandex_condition_to_russian).
"""
defaults = {
"current_condition": [
{
"temp_C": 22,
"FeelsLikeC": 24,
"weatherDesc": [{"value": "Ясно"}],
"humidity": 45,
"windspeedKmph": 5.0, # м/с
"wind_gust": 8.0,
"wind_dir": "n",
"pressure": 735.0,
}
]
}
defaults["current_condition"][0].update(extra)
return defaults
@pytest.mark.asyncio
async def test_pg_success(self) -> None:
"""Успешный запрос погоды должен отправить сообщение с данными."""
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 "Давление: 735.0 мм рт. ст." in args
@pytest.mark.asyncio
async def test_pg_fetch_returns_none(self) -> None:
"""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) -> None:
"""current_condition пустой список — graceful fallback."""
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()
assert "Не удалось получить данные о погоде" in ctx.send.call_args[0][0]
@pytest.mark.asyncio
async def test_pg_current_condition_none(self) -> None:
"""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) -> None:
"""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) -> None:
"""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) -> None:
"""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) -> None:
"""Поля с отсутствующими значениями должны давать ''."""
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]
# None значения корректно заменяются на "—"
assert "Температура: —°C" in args
assert "ощущается как —°C" in args
assert "Описание: —" in args
assert "Влажность: —%" in args
assert "Давление: — мм рт. ст." in args
@pytest.mark.asyncio
async def test_pg_russian_weather_description(self) -> None:
"""Описание погоды на русском должно корректно отображаться."""
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_with_wind_gust(self) -> None:
"""Порывы ветра должны отображаться."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(
windspeedKmph=3.0,
wind_gust=10.5,
wind_dir="n",
)
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 "Ветер: 3.0 м/с (порывы 10.5 м/с), северный" in args
@pytest.mark.asyncio
async def test_pg_with_wind_direction(self) -> None:
"""Направление ветра должно переводиться."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(
windspeedKmph=5.0,
wind_gust=8.0,
wind_dir="se",
)
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_float_values(self) -> None:
"""Float значения из Яндекс Погоды должны работать."""
cog = self._make_cog()
ctx = self._make_ctx()
weather = self._make_weather_data(
temp_C=17.3,
FeelsLikeC=16.8,
humidity=87.5,
windspeedKmph=2.1, # м/с
wind_gust=5.5,
wind_dir="n",
pressure=750.1,
)
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 "Температура: 17.3°C" in args
assert "Ветер: 2.1 м/с" in args
assert "Давление: 750.1 мм рт. ст." in args