"""Unit-тесты погодного сервиса. Сеть не используется: HTTP-слой подменяется. Запуск: pytest """ from __future__ import annotations import sys from pathlib import Path import pytest # weather.py лежит внутри каталога агента и не является пакетом, # поэтому для тестов добавляем его каталог в sys.path. AGENT_DIR = Path(__file__).resolve().parents[1] / "agents" / "custom" / "weather" sys.path.insert(0, str(AGENT_DIR)) import weather # noqa: E402 LOCATION = weather.Location("Казань", "Татарстан", "Россия", 55.7887, 49.1221) GEOCODE_PAYLOAD = { "results": [ { "name": "Казань", "admin1": "Татарстан", "country": "Россия", "latitude": 55.78874, "longitude": 49.12214, } ] } FORECAST_PAYLOAD = { "properties": { "timeseries": [ { "data": { "instant": { "details": { "air_temperature": 22.2, "relative_humidity": 54.0, "wind_speed": 4.1, "wind_from_direction": 103.0, } }, "next_1_hours": { "summary": {"symbol_code": "partlycloudy_day"}, "details": {"precipitation_amount": 0.0}, }, } } ] } } @pytest.fixture(autouse=True) def clear_geocode_cache(): """Кэш геокодера — глобальный, поэтому чистим его перед каждым тестом.""" weather.geocode.cache_clear() yield weather.geocode.cache_clear() # --- geocode --------------------------------------------------------------- def test_geocode_empty_city_raises(): with pytest.raises(weather.WeatherError) as exc: weather.geocode(" ") assert exc.value.message == "не указан город" def test_geocode_not_found(monkeypatch): monkeypatch.setattr(weather, "_get", lambda url, params: {"results": []}) with pytest.raises(weather.WeatherError) as exc: weather.geocode("Абракадабрания") assert "не найден" in exc.value.message def test_geocode_builds_location_and_rounds_coordinates(monkeypatch): monkeypatch.setattr(weather, "_get", lambda url, params: GEOCODE_PAYLOAD) location = weather.geocode("Казань") assert location.title == "Казань, Татарстан, Россия" # met.no принимает не более 4 знаков после запятой. assert location.latitude == 55.7887 assert location.longitude == 49.1221 def test_geocode_is_cached(monkeypatch): calls: list[str] = [] def fake_get(url, params): calls.append(params["name"]) return GEOCODE_PAYLOAD monkeypatch.setattr(weather, "_get", fake_get) weather.geocode("Казань") weather.geocode("Казань") assert calls == ["Казань"] # второй вызов обслужен из кэша # --- fetch_weather --------------------------------------------------------- def test_fetch_weather_maps_fields(monkeypatch): monkeypatch.setattr(weather, "_get", lambda url, params: FORECAST_PAYLOAD) current = weather.fetch_weather(LOCATION) assert current.temperature_c == 22.2 assert current.humidity_pct == 54 assert current.wind_ms == 4.1 assert current.wind_dir_deg == 103 assert current.precipitation_mm == 0.0 assert current.description == "переменная облачность" @pytest.mark.parametrize( ("symbol", "expected"), [("clearsky_night", "ясно"), ("snow", "снег"), ("unknowncode", "нет данных")], ) def test_fetch_weather_strips_symbol_suffix(monkeypatch, symbol, expected): payload = { "properties": { "timeseries": [ { "data": { "instant": { "details": { "air_temperature": 0.0, "relative_humidity": 90.0, "wind_speed": 1.0, "wind_from_direction": 0.0, } }, "next_1_hours": {"summary": {"symbol_code": symbol}, "details": {}}, } } ] } } monkeypatch.setattr(weather, "_get", lambda url, params: payload) assert weather.fetch_weather(LOCATION).description == expected def test_fetch_weather_bad_payload_raises(monkeypatch): monkeypatch.setattr(weather, "_get", lambda url, params: {}) with pytest.raises(weather.WeatherError): weather.fetch_weather(LOCATION) # --- _get ------------------------------------------------------------------ def test_get_does_not_retry_4xx(monkeypatch): calls = {"n": 0} class Response: status_code = 404 class Session: def get(self, *args, **kwargs): calls["n"] += 1 return Response() monkeypatch.setattr(weather, "_session", Session()) with pytest.raises(weather.WeatherError): weather._get("https://example.invalid", {}) assert calls["n"] == 1 # постоянную 4xx не повторяем # --- format_summary -------------------------------------------------------- def test_format_summary_contains_key_facts(): current = weather.Weather(22.2, 54, 4.1, 103, 0.0, "переменная облачность") text = weather.format_summary(LOCATION, current) assert "Казань, Татарстан, Россия" in text assert "переменная облачность" in text assert "22.2 °C" in text