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