Modernize SMHI weather (#97275)

* SMHI forecast service

* Mod weather

* reset weather

* Fix tests

* coverage

* add test
This commit is contained in:
G Johansson 2023-08-07 17:24:43 +02:00 committed by GitHub
parent 65365d1db5
commit 15eed166ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 10884 additions and 1305 deletions

View File

@ -40,6 +40,7 @@ from homeassistant.components.weather import (
ATTR_FORECAST_WIND_BEARING,
Forecast,
WeatherEntity,
WeatherEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@ -128,6 +129,9 @@ class SmhiWeather(WeatherEntity):
_attr_has_entity_name = True
_attr_name = None
_attr_supported_features = (
WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
)
def __init__(
self,
@ -138,7 +142,8 @@ class SmhiWeather(WeatherEntity):
) -> None:
"""Initialize the SMHI weather entity."""
self._attr_unique_id = f"{latitude}, {longitude}"
self._forecasts: list[SmhiForecast] | None = None
self._forecast_daily: list[SmhiForecast] | None = None
self._forecast_hourly: list[SmhiForecast] | None = None
self._fail_count = 0
self._smhi_api = Smhi(longitude, latitude, session=session)
self._attr_device_info = DeviceInfo(
@ -155,9 +160,9 @@ class SmhiWeather(WeatherEntity):
@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return additional attributes."""
if self._forecasts:
if self._forecast_daily:
return {
ATTR_SMHI_THUNDER_PROBABILITY: self._forecasts[0].thunder,
ATTR_SMHI_THUNDER_PROBABILITY: self._forecast_daily[0].thunder,
}
return None
@ -166,7 +171,8 @@ class SmhiWeather(WeatherEntity):
"""Refresh the forecast data from SMHI weather API."""
try:
async with async_timeout.timeout(TIMEOUT):
self._forecasts = await self._smhi_api.async_get_forecast()
self._forecast_daily = await self._smhi_api.async_get_forecast()
self._forecast_hourly = await self._smhi_api.async_get_forecast_hour()
self._fail_count = 0
except (asyncio.TimeoutError, SmhiForecastException):
_LOGGER.error("Failed to connect to SMHI API, retry in 5 minutes")
@ -175,23 +181,24 @@ class SmhiWeather(WeatherEntity):
async_call_later(self.hass, RETRY_TIMEOUT, self.retry_update)
return
if self._forecasts:
self._attr_native_temperature = self._forecasts[0].temperature
self._attr_humidity = self._forecasts[0].humidity
self._attr_native_wind_speed = self._forecasts[0].wind_speed
self._attr_wind_bearing = self._forecasts[0].wind_direction
self._attr_native_visibility = self._forecasts[0].horizontal_visibility
self._attr_native_pressure = self._forecasts[0].pressure
self._attr_native_wind_gust_speed = self._forecasts[0].wind_gust
self._attr_cloud_coverage = self._forecasts[0].cloudiness
if self._forecast_daily:
self._attr_native_temperature = self._forecast_daily[0].temperature
self._attr_humidity = self._forecast_daily[0].humidity
self._attr_native_wind_speed = self._forecast_daily[0].wind_speed
self._attr_wind_bearing = self._forecast_daily[0].wind_direction
self._attr_native_visibility = self._forecast_daily[0].horizontal_visibility
self._attr_native_pressure = self._forecast_daily[0].pressure
self._attr_native_wind_gust_speed = self._forecast_daily[0].wind_gust
self._attr_cloud_coverage = self._forecast_daily[0].cloudiness
self._attr_condition = next(
(
k
for k, v in CONDITION_CLASSES.items()
if self._forecasts[0].symbol in v
if self._forecast_daily[0].symbol in v
),
None,
)
await self.async_update_listeners(("daily", "hourly"))
async def retry_update(self, _: datetime) -> None:
"""Retry refresh weather forecast."""
@ -202,12 +209,12 @@ class SmhiWeather(WeatherEntity):
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
if self._forecasts is None or len(self._forecasts) < 2:
if self._forecast_daily is None or len(self._forecast_daily) < 2:
return None
data: list[Forecast] = []
for forecast in self._forecasts[1:]:
for forecast in self._forecast_daily[1:]:
condition = next(
(k for k, v in CONDITION_CLASSES.items() if forecast.symbol in v), None
)
@ -229,3 +236,43 @@ class SmhiWeather(WeatherEntity):
)
return data
def _get_forecast_data(
self, forecast_data: list[SmhiForecast] | None
) -> list[Forecast] | None:
"""Get forecast data."""
if forecast_data is None or len(forecast_data) < 3:
return None
data: list[Forecast] = []
for forecast in forecast_data[1:]:
condition = next(
(k for k, v in CONDITION_CLASSES.items() if forecast.symbol in v), None
)
data.append(
{
ATTR_FORECAST_TIME: forecast.valid_time.isoformat(),
ATTR_FORECAST_NATIVE_TEMP: forecast.temperature_max,
ATTR_FORECAST_NATIVE_TEMP_LOW: forecast.temperature_min,
ATTR_FORECAST_NATIVE_PRECIPITATION: forecast.total_precipitation,
ATTR_FORECAST_CONDITION: condition,
ATTR_FORECAST_NATIVE_PRESSURE: forecast.pressure,
ATTR_FORECAST_WIND_BEARING: forecast.wind_direction,
ATTR_FORECAST_NATIVE_WIND_SPEED: forecast.wind_speed,
ATTR_FORECAST_HUMIDITY: forecast.humidity,
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: forecast.wind_gust,
ATTR_FORECAST_CLOUD_COVERAGE: forecast.cloudiness,
}
)
return data
async def async_forecast_daily(self) -> list[Forecast] | None:
"""Service to retrieve the daily forecast."""
return self._get_forecast_data(self._forecast_daily)
async def async_forecast_hourly(self) -> list[Forecast] | None:
"""Service to retrieve the hourly forecast."""
return self._get_forecast_data(self._forecast_hourly)

View File

@ -1,10 +1,18 @@
"""Provide common smhi fixtures."""
import pytest
from homeassistant.components.smhi.const import DOMAIN
from tests.common import load_fixture
@pytest.fixture(scope="session")
def api_response():
"""Return an API response."""
return load_fixture("smhi.json")
return load_fixture("smhi.json", DOMAIN)
@pytest.fixture(scope="session")
def api_response_lack_data():
"""Return an API response."""
return load_fixture("smhi_short.json", DOMAIN)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,148 @@
{
"approvedTime": "2023-08-07T07:07:34Z",
"referenceTime": "2023-08-07T07:00:00Z",
"geometry": {
"type": "Point",
"coordinates": [[15.990068, 57.997072]]
},
"timeSeries": [
{
"validTime": "2023-08-07T08:00:00Z",
"parameters": [
{
"name": "spp",
"levelType": "hl",
"level": 0,
"unit": "percent",
"values": [-9]
},
{
"name": "pcat",
"levelType": "hl",
"level": 0,
"unit": "category",
"values": [0]
},
{
"name": "pmin",
"levelType": "hl",
"level": 0,
"unit": "kg/m2/h",
"values": [0.0]
},
{
"name": "pmean",
"levelType": "hl",
"level": 0,
"unit": "kg/m2/h",
"values": [0.0]
},
{
"name": "pmax",
"levelType": "hl",
"level": 0,
"unit": "kg/m2/h",
"values": [0.0]
},
{
"name": "pmedian",
"levelType": "hl",
"level": 0,
"unit": "kg/m2/h",
"values": [0.0]
},
{
"name": "tcc_mean",
"levelType": "hl",
"level": 0,
"unit": "octas",
"values": [8]
},
{
"name": "lcc_mean",
"levelType": "hl",
"level": 0,
"unit": "octas",
"values": [8]
},
{
"name": "mcc_mean",
"levelType": "hl",
"level": 0,
"unit": "octas",
"values": [7]
},
{
"name": "hcc_mean",
"levelType": "hl",
"level": 0,
"unit": "octas",
"values": [7]
},
{
"name": "t",
"levelType": "hl",
"level": 2,
"unit": "Cel",
"values": [18.4]
},
{
"name": "msl",
"levelType": "hmsl",
"level": 0,
"unit": "hPa",
"values": [992.4]
},
{
"name": "vis",
"levelType": "hl",
"level": 2,
"unit": "km",
"values": [0.4]
},
{
"name": "wd",
"levelType": "hl",
"level": 10,
"unit": "degree",
"values": [93]
},
{
"name": "ws",
"levelType": "hl",
"level": 10,
"unit": "m/s",
"values": [2.5]
},
{
"name": "r",
"levelType": "hl",
"level": 2,
"unit": "percent",
"values": [100]
},
{
"name": "tstm",
"levelType": "hl",
"level": 0,
"unit": "percent",
"values": [37]
},
{
"name": "gust",
"levelType": "hl",
"level": 10,
"unit": "m/s",
"values": [6.2]
},
{
"name": "Wsymb2",
"levelType": "hl",
"level": 0,
"unit": "category",
"values": [7]
}
]
}
]
}

View File

@ -0,0 +1,426 @@
# serializer version: 1
# name: test_forecast_daily
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T12:00:00',
'humidity': 96,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 18.0,
'templow': 15.0,
'wind_bearing': 114,
'wind_gust_speed': 32.76,
'wind_speed': 10.08,
})
# ---
# name: test_forecast_daily.1
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-13T12:00:00',
'humidity': 59,
'precipitation': 0.0,
'pressure': 1013.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 234,
'wind_gust_speed': 35.64,
'wind_speed': 14.76,
})
# ---
# name: test_forecast_daily.2
dict({
'cloud_coverage': 100,
'condition': 'fog',
'datetime': '2023-08-07T09:00:00',
'humidity': 100,
'precipitation': 0.0,
'pressure': 992.0,
'temperature': 18.0,
'templow': 18.0,
'wind_bearing': 103,
'wind_gust_speed': 23.76,
'wind_speed': 9.72,
})
# ---
# name: test_forecast_daily.3
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T15:00:00',
'humidity': 89,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 16.0,
'templow': 16.0,
'wind_bearing': 108,
'wind_gust_speed': 31.68,
'wind_speed': 12.24,
})
# ---
# name: test_forecast_service
dict({
'forecast': list([
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T12:00:00',
'humidity': 96,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 18.0,
'templow': 15.0,
'wind_bearing': 114,
'wind_gust_speed': 32.76,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-08T12:00:00',
'humidity': 97,
'precipitation': 10.6,
'pressure': 984.0,
'temperature': 15.0,
'templow': 11.0,
'wind_bearing': 183,
'wind_gust_speed': 27.36,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-09T12:00:00',
'humidity': 95,
'precipitation': 6.3,
'pressure': 1001.0,
'temperature': 12.0,
'templow': 11.0,
'wind_bearing': 166,
'wind_gust_speed': 48.24,
'wind_speed': 18.0,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-10T12:00:00',
'humidity': 75,
'precipitation': 4.8,
'pressure': 1011.0,
'temperature': 14.0,
'templow': 10.0,
'wind_bearing': 174,
'wind_gust_speed': 29.16,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-11T12:00:00',
'humidity': 69,
'precipitation': 0.6,
'pressure': 1015.0,
'temperature': 18.0,
'templow': 12.0,
'wind_bearing': 197,
'wind_gust_speed': 27.36,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-12T12:00:00',
'humidity': 82,
'precipitation': 0.0,
'pressure': 1014.0,
'temperature': 17.0,
'templow': 12.0,
'wind_bearing': 225,
'wind_gust_speed': 28.08,
'wind_speed': 8.64,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-13T12:00:00',
'humidity': 59,
'precipitation': 0.0,
'pressure': 1013.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 234,
'wind_gust_speed': 35.64,
'wind_speed': 14.76,
}),
dict({
'cloud_coverage': 100,
'condition': 'partlycloudy',
'datetime': '2023-08-14T12:00:00',
'humidity': 56,
'precipitation': 0.0,
'pressure': 1015.0,
'temperature': 21.0,
'templow': 14.0,
'wind_bearing': 216,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 88,
'condition': 'partlycloudy',
'datetime': '2023-08-15T12:00:00',
'humidity': 64,
'precipitation': 3.6,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 226,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-16T12:00:00',
'humidity': 61,
'precipitation': 2.4,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 233,
'wind_gust_speed': 33.48,
'wind_speed': 14.04,
}),
]),
})
# ---
# name: test_forecast_services
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T12:00:00',
'humidity': 96,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 18.0,
'templow': 15.0,
'wind_bearing': 114,
'wind_gust_speed': 32.76,
'wind_speed': 10.08,
})
# ---
# name: test_forecast_services.1
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-13T12:00:00',
'humidity': 59,
'precipitation': 0.0,
'pressure': 1013.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 234,
'wind_gust_speed': 35.64,
'wind_speed': 14.76,
})
# ---
# name: test_forecast_services.2
dict({
'cloud_coverage': 100,
'condition': 'fog',
'datetime': '2023-08-07T09:00:00',
'humidity': 100,
'precipitation': 0.0,
'pressure': 992.0,
'temperature': 18.0,
'templow': 18.0,
'wind_bearing': 103,
'wind_gust_speed': 23.76,
'wind_speed': 9.72,
})
# ---
# name: test_forecast_services.3
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T15:00:00',
'humidity': 89,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 16.0,
'templow': 16.0,
'wind_bearing': 108,
'wind_gust_speed': 31.68,
'wind_speed': 12.24,
})
# ---
# name: test_setup_hass
ReadOnlyDict({
'apparent_temperature': 18.0,
'attribution': 'Swedish weather institute (SMHI)',
'cloud_coverage': 100,
'forecast': list([
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T12:00:00',
'humidity': 96,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 18.0,
'templow': 15.0,
'wind_bearing': 114,
'wind_gust_speed': 32.76,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-08T12:00:00',
'humidity': 97,
'precipitation': 10.6,
'pressure': 984.0,
'temperature': 15.0,
'templow': 11.0,
'wind_bearing': 183,
'wind_gust_speed': 27.36,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-09T12:00:00',
'humidity': 95,
'precipitation': 6.3,
'pressure': 1001.0,
'temperature': 12.0,
'templow': 11.0,
'wind_bearing': 166,
'wind_gust_speed': 48.24,
'wind_speed': 18.0,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-10T12:00:00',
'humidity': 75,
'precipitation': 4.8,
'pressure': 1011.0,
'temperature': 14.0,
'templow': 10.0,
'wind_bearing': 174,
'wind_gust_speed': 29.16,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-11T12:00:00',
'humidity': 69,
'precipitation': 0.6,
'pressure': 1015.0,
'temperature': 18.0,
'templow': 12.0,
'wind_bearing': 197,
'wind_gust_speed': 27.36,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-12T12:00:00',
'humidity': 82,
'precipitation': 0.0,
'pressure': 1014.0,
'temperature': 17.0,
'templow': 12.0,
'wind_bearing': 225,
'wind_gust_speed': 28.08,
'wind_speed': 8.64,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-13T12:00:00',
'humidity': 59,
'precipitation': 0.0,
'pressure': 1013.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 234,
'wind_gust_speed': 35.64,
'wind_speed': 14.76,
}),
dict({
'cloud_coverage': 100,
'condition': 'partlycloudy',
'datetime': '2023-08-14T12:00:00',
'humidity': 56,
'precipitation': 0.0,
'pressure': 1015.0,
'temperature': 21.0,
'templow': 14.0,
'wind_bearing': 216,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 88,
'condition': 'partlycloudy',
'datetime': '2023-08-15T12:00:00',
'humidity': 64,
'precipitation': 3.6,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 226,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-16T12:00:00',
'humidity': 61,
'precipitation': 2.4,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 233,
'wind_gust_speed': 33.48,
'wind_speed': 14.04,
}),
]),
'friendly_name': 'test',
'humidity': 100,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
'pressure': 992.0,
'pressure_unit': <UnitOfPressure.HPA: 'hPa'>,
'supported_features': <WeatherEntityFeature: 3>,
'temperature': 18.0,
'temperature_unit': <UnitOfTemperature.CELSIUS: '°C'>,
'thunder_probability': 37,
'visibility': 0.4,
'visibility_unit': <UnitOfLength.KILOMETERS: 'km'>,
'wind_bearing': 93,
'wind_gust_speed': 22.32,
'wind_speed': 9.0,
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
})
# ---
# name: test_setup_hass.1
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-08T12:00:00',
'humidity': 97,
'precipitation': 10.6,
'pressure': 984.0,
'temperature': 15.0,
'templow': 11.0,
'wind_bearing': 183,
'wind_gust_speed': 27.36,
'wind_speed': 11.16,
})
# ---

View File

@ -5,21 +5,16 @@ from unittest.mock import patch
import pytest
from smhi.smhi_lib import APIURL_TEMPLATE, SmhiForecast, SmhiForecastException
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.smhi.const import ATTR_SMHI_THUNDER_PROBABILITY
from homeassistant.components.smhi.weather import CONDITION_CLASSES, RETRY_TIMEOUT
from homeassistant.components.smhi.weather import (
CONDITION_CLASSES,
RETRY_TIMEOUT,
)
from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_FORECAST_CLOUD_COVERAGE,
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_PRESSURE,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_GUST_SPEED,
ATTR_FORECAST_WIND_SPEED,
ATTR_WEATHER_HUMIDITY,
ATTR_WEATHER_PRESSURE,
ATTR_WEATHER_TEMPERATURE,
@ -28,6 +23,7 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_SPEED,
ATTR_WEATHER_WIND_SPEED_UNIT,
DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST,
)
from homeassistant.components.weather.const import (
ATTR_WEATHER_CLOUD_COVERAGE,
@ -42,10 +38,14 @@ from . import ENTITY_ID, TEST_CONFIG
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import WebSocketGenerator
async def test_setup_hass(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
api_response: str,
snapshot: SnapshotAssertion,
) -> None:
"""Test for successfully setting up the smhi integration."""
uri = APIURL_TEMPLATE.format(
@ -58,37 +58,19 @@ async def test_setup_hass(
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 1
assert aioclient_mock.call_count == 2
# Testing the actual entity state for
# deeper testing than normal unity test
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == "sunny"
assert state.attributes[ATTR_WEATHER_CLOUD_COVERAGE] == 50
assert state.attributes[ATTR_SMHI_THUNDER_PROBABILITY] == 33
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 16.92
assert state.attributes[ATTR_ATTRIBUTION].find("SMHI") >= 0
assert state.attributes[ATTR_WEATHER_HUMIDITY] == 55
assert state.attributes[ATTR_WEATHER_PRESSURE] == 1024
assert state.attributes[ATTR_WEATHER_TEMPERATURE] == 17
assert state.attributes[ATTR_WEATHER_VISIBILITY] == 50
assert state.attributes[ATTR_WEATHER_WIND_SPEED] == 6.84
assert state.attributes[ATTR_WEATHER_WIND_BEARING] == 134
assert len(state.attributes["forecast"]) == 4
assert state.state == "fog"
assert state.attributes == snapshot
assert len(state.attributes["forecast"]) == 10
forecast = state.attributes["forecast"][1]
assert forecast[ATTR_FORECAST_TIME] == "2018-09-02T12:00:00"
assert forecast[ATTR_FORECAST_TEMP] == 21
assert forecast[ATTR_FORECAST_TEMP_LOW] == 6
assert forecast[ATTR_FORECAST_PRECIPITATION] == 0
assert forecast[ATTR_FORECAST_CONDITION] == "partlycloudy"
assert forecast[ATTR_FORECAST_PRESSURE] == 1026
assert forecast[ATTR_FORECAST_WIND_BEARING] == 203
assert forecast[ATTR_FORECAST_WIND_SPEED] == 6.12
assert forecast[ATTR_FORECAST_WIND_GUST_SPEED] == 18.36
assert forecast[ATTR_FORECAST_CLOUD_COVERAGE] == 100
assert forecast == snapshot
async def test_properties_no_data(hass: HomeAssistant) -> None:
@ -188,6 +170,9 @@ async def test_properties_unknown_symbol(hass: HomeAssistant) -> None:
with patch(
"homeassistant.components.smhi.weather.Smhi.async_get_forecast",
return_value=testdata,
), patch(
"homeassistant.components.smhi.weather.Smhi.async_get_forecast_hour",
return_value=None,
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -341,7 +326,7 @@ async def test_custom_speed_unit(
assert state
assert state.name == "test"
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 16.92
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 22.32
entity_reg = er.async_get(hass)
entity_reg.async_update_entity_options(
@ -353,4 +338,137 @@ async def test_custom_speed_unit(
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 4.7
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 6.2
async def test_forecast_services(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
aioclient_mock: AiohttpClientMocker,
api_response: str,
snapshot: SnapshotAssertion,
) -> None:
"""Test multiple forecast."""
uri = APIURL_TEMPLATE.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
client = await hass_ws_client(hass)
await client.send_json_auto_id(
{
"type": "weather/subscribe_forecast",
"forecast_type": "daily",
"entity_id": ENTITY_ID,
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] is None
subscription_id = msg["id"]
msg = await client.receive_json()
assert msg["id"] == subscription_id
assert msg["type"] == "event"
forecast1 = msg["event"]["forecast"]
assert len(forecast1) == 10
assert forecast1[0] == snapshot
assert forecast1[6] == snapshot
await client.send_json_auto_id(
{
"type": "weather/subscribe_forecast",
"forecast_type": "hourly",
"entity_id": ENTITY_ID,
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] is None
subscription_id = msg["id"]
msg = await client.receive_json()
assert msg["id"] == subscription_id
assert msg["type"] == "event"
forecast1 = msg["event"]["forecast"]
assert len(forecast1) == 72
assert forecast1[0] == snapshot
assert forecast1[6] == snapshot
async def test_forecast_services_lack_of_data(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
aioclient_mock: AiohttpClientMocker,
api_response_lack_data: str,
snapshot: SnapshotAssertion,
) -> None:
"""Test forecast lacking data."""
uri = APIURL_TEMPLATE.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response_lack_data)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
client = await hass_ws_client(hass)
await client.send_json_auto_id(
{
"type": "weather/subscribe_forecast",
"forecast_type": "daily",
"entity_id": ENTITY_ID,
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] is None
subscription_id = msg["id"]
msg = await client.receive_json()
assert msg["id"] == subscription_id
assert msg["type"] == "event"
forecast1 = msg["event"]["forecast"]
assert forecast1 is None
async def test_forecast_service(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
api_response: str,
snapshot: SnapshotAssertion,
) -> None:
"""Test forecast service."""
uri = APIURL_TEMPLATE.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECAST,
{"entity_id": ENTITY_ID, "type": "daily"},
blocking=True,
return_response=True,
)
assert response == snapshot

1252
tests/fixtures/smhi.json vendored

File diff suppressed because it is too large Load Diff