Files
ams-weather/tests/test_weather.py
T

181 lines
5.9 KiB
Python

"""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