import pytest from unittest.mock import AsyncMock, MagicMock, patch from commands.pg import Pg class TestPgInit: """Тесты инициализации Cog Pg.""" def test_uses_api_url_constant(self) -> None: """Pg использует API_URL_WEATHER напрямую (без инстанс-переменной).""" from utils.pogoda import API_URL_WEATHER cog = Pg() # Pg не хранит api_url как инстанс-переменную — использует константу напрямую assert not hasattr(cog, "api_url") assert API_URL_WEATHER == "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) -> None: """Успешный запрос погоды должен отправить 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) -> 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_translate_unknown_weather(self) -> None: """Неизвестное описание погоды должно возвращать оригинал.""" 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) -> 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_negative_pressure(self) -> None: """Отрицательное давление должно конвертироваться.""" 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) -> None: """Большая скорость ветра должна корректно округляться.""" 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