Files
2026-07-26 21:32:22 +02:00

262 lines
9.3 KiB
Python

"""Test Meteo.lt weather entity."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
from meteo_lt import Forecast
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.meteo_lt.const import DOMAIN
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY,
ATTR_CONDITION_EXCEPTIONAL,
ATTR_CONDITION_FOG,
ATTR_CONDITION_HAIL,
ATTR_CONDITION_LIGHTNING,
ATTR_CONDITION_LIGHTNING_RAINY,
ATTR_CONDITION_PARTLYCLOUDY,
ATTR_CONDITION_POURING,
ATTR_CONDITION_RAINY,
ATTR_CONDITION_SNOWY,
ATTR_CONDITION_SNOWY_RAINY,
ATTR_CONDITION_SUNNY,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util import dt as dt_util
from tests.common import (
MockConfigEntry,
async_load_json_object_fixture,
snapshot_platform,
)
@pytest.fixture(autouse=True)
def override_platforms() -> Generator[None]:
"""Override PLATFORMS."""
with patch("homeassistant.components.meteo_lt.PLATFORMS", [Platform.WEATHER]):
yield
@pytest.mark.freeze_time("2025-09-25 10:00:00")
async def test_weather_entity(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test weather entity."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.freeze_time("2025-09-25 10:00:00")
async def test_coordinator_requests_forecast_without_warnings(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_meteo_lt_api: AsyncMock,
) -> None:
"""Test that the coordinator fetches forecasts with warnings disabled."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.LOADED
mock_meteo_lt_api.get_forecast.assert_called_with("vilnius", include_warnings=False)
@pytest.mark.parametrize(
"expected_condition",
[
pytest.param(
ATTR_CONDITION_SUNNY,
marks=pytest.mark.freeze_time("2025-09-25 10:00:00"), # 13:00 in Vilnius
id="day",
),
pytest.param(
ATTR_CONDITION_CLEAR_NIGHT,
marks=pytest.mark.freeze_time("2025-09-25 22:00:00"), # 01:00 in Vilnius
id="night",
),
],
)
async def test_condition_clear_maps_day_night(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_meteo_lt_api: AsyncMock,
expected_condition: str,
) -> None:
"""Test that a clear condition maps to sunny or clear-night by sun position at the forecast time."""
hass.config.latitude = 54.68705 # Vilnius, matching the forecast place
hass.config.longitude = 25.28291
forecast_data = await async_load_json_object_fixture(hass, "forecast.json", DOMAIN)
current_hour = dt_util.utcnow().strftime("%Y-%m-%d %H:%M:%S")
forecast_data["forecastTimestamps"][0]["forecastTimeUtc"] = current_hour
forecast_data["forecastTimestamps"][0]["conditionCode"] = "clear"
mock_meteo_lt_api.get_forecast.side_effect = lambda *args, **kwargs: (
Forecast.from_dict(forecast_data)
)
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.vilnius")
assert state is not None
assert state.state == expected_condition
@pytest.mark.freeze_time("2025-09-25 10:00:00")
@pytest.mark.parametrize(
("condition_code", "expected_condition"),
[
pytest.param("partly-cloudy", ATTR_CONDITION_PARTLYCLOUDY, id="partly-cloudy"),
pytest.param(
"cloudy-with-sunny-intervals",
ATTR_CONDITION_PARTLYCLOUDY,
id="cloudy-with-sunny-intervals",
),
pytest.param("cloudy", ATTR_CONDITION_CLOUDY, id="cloudy"),
pytest.param("thunder", ATTR_CONDITION_LIGHTNING, id="thunder"),
pytest.param(
"isolated-thunderstorms",
ATTR_CONDITION_LIGHTNING_RAINY,
id="isolated-thunderstorms",
),
pytest.param(
"thunderstorms", ATTR_CONDITION_LIGHTNING_RAINY, id="thunderstorms"
),
pytest.param(
"heavy-rain-with-thunderstorms",
ATTR_CONDITION_LIGHTNING_RAINY,
id="heavy-rain-with-thunderstorms",
),
pytest.param("light-rain", ATTR_CONDITION_RAINY, id="light-rain"),
pytest.param("rain", ATTR_CONDITION_RAINY, id="rain"),
pytest.param("heavy-rain", ATTR_CONDITION_POURING, id="heavy-rain"),
pytest.param("light-sleet", ATTR_CONDITION_SNOWY_RAINY, id="light-sleet"),
pytest.param("sleet", ATTR_CONDITION_SNOWY_RAINY, id="sleet"),
pytest.param("freezing-rain", ATTR_CONDITION_SNOWY_RAINY, id="freezing-rain"),
pytest.param("hail", ATTR_CONDITION_HAIL, id="hail"),
pytest.param("light-snow", ATTR_CONDITION_SNOWY, id="light-snow"),
pytest.param("snow", ATTR_CONDITION_SNOWY, id="snow"),
pytest.param("heavy-snow", ATTR_CONDITION_SNOWY, id="heavy-snow"),
pytest.param("fog", ATTR_CONDITION_FOG, id="fog"),
],
)
async def test_condition_code_mapping(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_meteo_lt_api: AsyncMock,
condition_code: str,
expected_condition: str,
) -> None:
"""Test that each meteo.lt condition code maps to the expected HA condition."""
forecast_data = await async_load_json_object_fixture(hass, "forecast.json", DOMAIN)
forecast_data["forecastTimestamps"][0]["conditionCode"] = condition_code
mock_meteo_lt_api.get_forecast.side_effect = lambda *args, **kwargs: (
Forecast.from_dict(forecast_data)
)
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.vilnius")
assert state is not None
assert state.state == expected_condition
@pytest.mark.freeze_time("2025-09-25 10:00:00")
@pytest.mark.parametrize(
"condition_code",
[None, "not-a-real-condition"],
ids=["missing", "unknown"],
)
async def test_condition_unknown_maps_to_exceptional(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_meteo_lt_api: AsyncMock,
condition_code: str | None,
) -> None:
"""Test that a missing or unmapped condition code maps to exceptional."""
forecast_data = await async_load_json_object_fixture(hass, "forecast.json", DOMAIN)
forecast_data["forecastTimestamps"][0]["conditionCode"] = condition_code
mock_meteo_lt_api.get_forecast.side_effect = lambda *args, **kwargs: (
Forecast.from_dict(forecast_data)
)
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.vilnius")
assert state is not None
assert state.state == ATTR_CONDITION_EXCEPTIONAL
@pytest.mark.freeze_time("2025-09-25 10:00:00")
async def test_forecast_hourly(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test hourly forecast returns all entries with correct condition mapping."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.services.async_call(
"weather",
"get_forecasts",
{"entity_id": "weather.vilnius", "type": "hourly"},
blocking=True,
return_response=True,
)
forecasts = result["weather.vilnius"]["forecast"]
assert len(forecasts) == 8
assert forecasts[0]["condition"] == ATTR_CONDITION_PARTLYCLOUDY # 11:00
assert forecasts[1]["condition"] == ATTR_CONDITION_CLOUDY # 12:00
assert forecasts[3]["condition"] == ATTR_CONDITION_RAINY # 2025-09-27
@pytest.mark.freeze_time("2025-09-25 10:00:00")
async def test_forecast_daily(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test daily forecast aggregates hourly entries into per-day summaries."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.services.async_call(
"weather",
"get_forecasts",
{"entity_id": "weather.vilnius", "type": "daily"},
blocking=True,
return_response=True,
)
forecasts = result["weather.vilnius"]["forecast"]
assert len(forecasts) == 7
first_day = forecasts[0]
assert first_day["temperature"] == 13.5 # max(12.2, 13.5)
assert first_day["templow"] == 12.2 # min(12.2, 13.5)
assert first_day["precipitation"] == pytest.approx(0.1) # sum: 0 + 0.1
assert first_day["condition"] == ATTR_CONDITION_CLOUDY # midday 12:00 → "cloudy"
rainy_day = forecasts[2]
assert rainy_day["condition"] == ATTR_CONDITION_RAINY # 2025-09-27 → "rain"