mirror of
https://github.com/home-assistant/core.git
synced 2026-08-28 10:16:02 -05:00
262 lines
9.3 KiB
Python
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"
|