Deprecate weather.get_forecast (#102534)

* Deprecate weather.get_forecast

* Rename forecast to get_forecasts

* raise issue for use of deprecated service

* Add fix_flow

* Add service translation/yaml
This commit is contained in:
Kevin Stillhammer 2023-11-19 20:44:02 +01:00 committed by GitHub
parent 41224f1674
commit 173f4760bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 18726 additions and 76 deletions

View file

@ -135,7 +135,9 @@ SCAN_INTERVAL = timedelta(seconds=30)
ROUNDING_PRECISION = 2 ROUNDING_PRECISION = 2
SERVICE_GET_FORECAST: Final = "get_forecast" LEGACY_SERVICE_GET_FORECAST: Final = "get_forecast"
"""Deprecated: please use SERVICE_GET_FORECASTS."""
SERVICE_GET_FORECASTS: Final = "get_forecasts"
_ObservationUpdateCoordinatorT = TypeVar( _ObservationUpdateCoordinatorT = TypeVar(
"_ObservationUpdateCoordinatorT", bound="DataUpdateCoordinator[Any]" "_ObservationUpdateCoordinatorT", bound="DataUpdateCoordinator[Any]"
@ -211,7 +213,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_LOGGER, DOMAIN, hass, SCAN_INTERVAL _LOGGER, DOMAIN, hass, SCAN_INTERVAL
) )
component.async_register_legacy_entity_service( component.async_register_legacy_entity_service(
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
{vol.Required("type"): vol.In(("daily", "hourly", "twice_daily"))}, {vol.Required("type"): vol.In(("daily", "hourly", "twice_daily"))},
async_get_forecast_service, async_get_forecast_service,
required_features=[ required_features=[
@ -221,6 +223,17 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
], ],
supports_response=SupportsResponse.ONLY, supports_response=SupportsResponse.ONLY,
) )
component.async_register_entity_service(
SERVICE_GET_FORECASTS,
{vol.Required("type"): vol.In(("daily", "hourly", "twice_daily"))},
async_get_forecasts_service,
required_features=[
WeatherEntityFeature.FORECAST_DAILY,
WeatherEntityFeature.FORECAST_HOURLY,
WeatherEntityFeature.FORECAST_TWICE_DAILY,
],
supports_response=SupportsResponse.ONLY,
)
async_setup_ws_api(hass) async_setup_ws_api(hass)
await component.async_setup(config) await component.async_setup(config)
return True return True
@ -1086,6 +1099,32 @@ def raise_unsupported_forecast(entity_id: str, forecast_type: str) -> None:
async def async_get_forecast_service( async def async_get_forecast_service(
weather: WeatherEntity, service_call: ServiceCall weather: WeatherEntity, service_call: ServiceCall
) -> ServiceResponse:
"""Get weather forecast.
Deprecated: please use async_get_forecasts_service.
"""
_LOGGER.warning(
"Detected use of service 'weather.get_forecast'. "
"This is deprecated and will stop working in Home Assistant 2024.6. "
"Use 'weather.get_forecasts' instead which supports multiple entities",
)
ir.async_create_issue(
weather.hass,
DOMAIN,
"deprecated_service_weather_get_forecast",
breaks_in_ha_version="2024.6.0",
is_fixable=True,
is_persistent=False,
issue_domain=weather.platform.platform_name,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_service_weather_get_forecast",
)
return await async_get_forecasts_service(weather, service_call)
async def async_get_forecasts_service(
weather: WeatherEntity, service_call: ServiceCall
) -> ServiceResponse: ) -> ServiceResponse:
"""Get weather forecast.""" """Get weather forecast."""
forecast_type = service_call.data["type"] forecast_type = service_call.data["type"]

View file

@ -16,3 +16,21 @@ get_forecast:
- "hourly" - "hourly"
- "twice_daily" - "twice_daily"
translation_key: forecast_type translation_key: forecast_type
get_forecasts:
target:
entity:
domain: weather
supported_features:
- weather.WeatherEntityFeature.FORECAST_DAILY
- weather.WeatherEntityFeature.FORECAST_HOURLY
- weather.WeatherEntityFeature.FORECAST_TWICE_DAILY
fields:
type:
required: true
selector:
select:
options:
- "daily"
- "hourly"
- "twice_daily"
translation_key: forecast_type

View file

@ -88,13 +88,23 @@
} }
}, },
"services": { "services": {
"get_forecasts": {
"name": "Get forecasts",
"description": "Get weather forecasts.",
"fields": {
"type": {
"name": "Forecast type",
"description": "Forecast type: daily, hourly or twice daily."
}
}
},
"get_forecast": { "get_forecast": {
"name": "Get forecast", "name": "Get forecast",
"description": "Get weather forecast.", "description": "Get weather forecast.",
"fields": { "fields": {
"type": { "type": {
"name": "Forecast type", "name": "[%key:component::weather::services::get_forecasts::fields::type::name%]",
"description": "Forecast type: daily, hourly or twice daily." "description": "[%key:component::weather::services::get_forecasts::fields::type::description%]"
} }
} }
} }
@ -107,6 +117,17 @@
"deprecated_weather_forecast_no_url": { "deprecated_weather_forecast_no_url": {
"title": "[%key:component::weather::issues::deprecated_weather_forecast_url::title%]", "title": "[%key:component::weather::issues::deprecated_weather_forecast_url::title%]",
"description": "The custom integration `{platform}` implements the `forecast` property or sets `self._attr_forecast` in a subclass of WeatherEntity.\n\nPlease report it to the author of the {platform} integration.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue." "description": "The custom integration `{platform}` implements the `forecast` property or sets `self._attr_forecast` in a subclass of WeatherEntity.\n\nPlease report it to the author of the {platform} integration.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue."
},
"deprecated_service_weather_get_forecast": {
"title": "Detected use of deprecated service `weather.get_forecast`",
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::weather::issues::deprecated_service_weather_get_forecast::title%]",
"description": "Use `weather.get_forecasts` instead which supports multiple entities.\n\nPlease replace this service and adjust your automations and scripts and select **submit** to close this issue."
}
}
}
} }
} }
} }

View file

@ -75,6 +75,238 @@
]), ]),
}) })
# --- # ---
# name: test_forecast_service[forecast]
dict({
'weather.home': dict({
'forecast': list([
dict({
'apparent_temperature': 29.8,
'cloud_coverage': 58,
'condition': 'lightning-rainy',
'datetime': '2020-07-26T05:00:00+00:00',
'precipitation': 2.5,
'precipitation_probability': 60,
'temperature': 29.5,
'templow': 15.4,
'uv_index': 5,
'wind_bearing': 166,
'wind_gust_speed': 29.6,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 28.9,
'cloud_coverage': 52,
'condition': 'partlycloudy',
'datetime': '2020-07-27T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 25,
'temperature': 26.2,
'templow': 15.9,
'uv_index': 7,
'wind_bearing': 297,
'wind_gust_speed': 14.8,
'wind_speed': 9.3,
}),
dict({
'apparent_temperature': 31.6,
'cloud_coverage': 65,
'condition': 'partlycloudy',
'datetime': '2020-07-28T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 10,
'temperature': 31.7,
'templow': 16.8,
'uv_index': 7,
'wind_bearing': 198,
'wind_gust_speed': 24.1,
'wind_speed': 16.7,
}),
dict({
'apparent_temperature': 26.5,
'cloud_coverage': 45,
'condition': 'partlycloudy',
'datetime': '2020-07-29T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 9,
'temperature': 24.0,
'templow': 11.7,
'uv_index': 6,
'wind_bearing': 293,
'wind_gust_speed': 24.1,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 22.2,
'cloud_coverage': 50,
'condition': 'partlycloudy',
'datetime': '2020-07-30T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 1,
'temperature': 21.4,
'templow': 12.2,
'uv_index': 7,
'wind_bearing': 280,
'wind_gust_speed': 27.8,
'wind_speed': 18.5,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecast]
dict({
'forecast': list([
dict({
'apparent_temperature': 29.8,
'cloud_coverage': 58,
'condition': 'lightning-rainy',
'datetime': '2020-07-26T05:00:00+00:00',
'precipitation': 2.5,
'precipitation_probability': 60,
'temperature': 29.5,
'templow': 15.4,
'uv_index': 5,
'wind_bearing': 166,
'wind_gust_speed': 29.6,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 28.9,
'cloud_coverage': 52,
'condition': 'partlycloudy',
'datetime': '2020-07-27T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 25,
'temperature': 26.2,
'templow': 15.9,
'uv_index': 7,
'wind_bearing': 297,
'wind_gust_speed': 14.8,
'wind_speed': 9.3,
}),
dict({
'apparent_temperature': 31.6,
'cloud_coverage': 65,
'condition': 'partlycloudy',
'datetime': '2020-07-28T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 10,
'temperature': 31.7,
'templow': 16.8,
'uv_index': 7,
'wind_bearing': 198,
'wind_gust_speed': 24.1,
'wind_speed': 16.7,
}),
dict({
'apparent_temperature': 26.5,
'cloud_coverage': 45,
'condition': 'partlycloudy',
'datetime': '2020-07-29T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 9,
'temperature': 24.0,
'templow': 11.7,
'uv_index': 6,
'wind_bearing': 293,
'wind_gust_speed': 24.1,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 22.2,
'cloud_coverage': 50,
'condition': 'partlycloudy',
'datetime': '2020-07-30T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 1,
'temperature': 21.4,
'templow': 12.2,
'uv_index': 7,
'wind_bearing': 280,
'wind_gust_speed': 27.8,
'wind_speed': 18.5,
}),
]),
})
# ---
# name: test_forecast_service[get_forecasts]
dict({
'weather.home': dict({
'forecast': list([
dict({
'apparent_temperature': 29.8,
'cloud_coverage': 58,
'condition': 'lightning-rainy',
'datetime': '2020-07-26T05:00:00+00:00',
'precipitation': 2.5,
'precipitation_probability': 60,
'temperature': 29.5,
'templow': 15.4,
'uv_index': 5,
'wind_bearing': 166,
'wind_gust_speed': 29.6,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 28.9,
'cloud_coverage': 52,
'condition': 'partlycloudy',
'datetime': '2020-07-27T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 25,
'temperature': 26.2,
'templow': 15.9,
'uv_index': 7,
'wind_bearing': 297,
'wind_gust_speed': 14.8,
'wind_speed': 9.3,
}),
dict({
'apparent_temperature': 31.6,
'cloud_coverage': 65,
'condition': 'partlycloudy',
'datetime': '2020-07-28T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 10,
'temperature': 31.7,
'templow': 16.8,
'uv_index': 7,
'wind_bearing': 198,
'wind_gust_speed': 24.1,
'wind_speed': 16.7,
}),
dict({
'apparent_temperature': 26.5,
'cloud_coverage': 45,
'condition': 'partlycloudy',
'datetime': '2020-07-29T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 9,
'temperature': 24.0,
'templow': 11.7,
'uv_index': 6,
'wind_bearing': 293,
'wind_gust_speed': 24.1,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 22.2,
'cloud_coverage': 50,
'condition': 'partlycloudy',
'datetime': '2020-07-30T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 1,
'temperature': 21.4,
'templow': 12.2,
'uv_index': 7,
'wind_bearing': 280,
'wind_gust_speed': 27.8,
'wind_speed': 18.5,
}),
]),
}),
})
# ---
# name: test_forecast_subscription # name: test_forecast_subscription
list([ list([
dict({ dict({

View file

@ -3,6 +3,7 @@ from datetime import timedelta
from unittest.mock import PropertyMock, patch from unittest.mock import PropertyMock, patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.components.accuweather.const import ATTRIBUTION from homeassistant.components.accuweather.const import ATTRIBUTION
@ -31,7 +32,8 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_GUST_SPEED, ATTR_WEATHER_WIND_GUST_SPEED,
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
WeatherEntityFeature, WeatherEntityFeature,
) )
from homeassistant.const import ( from homeassistant.const import (
@ -206,16 +208,24 @@ async def test_unsupported_condition_icon_data(hass: HomeAssistant) -> None:
assert state.attributes.get(ATTR_FORECAST_CONDITION) is None assert state.attributes.get(ATTR_FORECAST_CONDITION) is None
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_forecast_service( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
await init_integration(hass, forecast=True) await init_integration(hass, forecast=True)
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.home", "entity_id": "weather.home",
"type": "daily", "type": "daily",
@ -223,7 +233,6 @@ async def test_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,8 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_GUST_SPEED, ATTR_WEATHER_WIND_GUST_SPEED,
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
) )
from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -122,10 +123,18 @@ async def test_aemet_weather_legacy(
assert state is None assert state is None
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_forecast_service( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
@ -135,7 +144,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.aemet", "entity_id": "weather.aemet",
"type": "daily", "type": "daily",
@ -147,7 +156,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.aemet", "entity_id": "weather.aemet",
"type": "hourly", "type": "hourly",

View file

@ -36,6 +36,125 @@
]), ]),
}) })
# --- # ---
# name: test_forecast_service[forecast]
dict({
'weather.hometown': dict({
'forecast': list([
dict({
'condition': 'rainy',
'datetime': datetime.datetime(2020, 1, 16, 0, 0),
'precipitation_probability': '100.0',
'temperature': 16.2,
'templow': 10.6,
'wind_bearing': 'S',
'wind_speed': 10.0,
}),
]),
}),
})
# ---
# name: test_forecast_service[forecast].1
dict({
'weather.hometown': dict({
'forecast': list([
dict({
'condition': 'rainy',
'datetime': datetime.datetime(2020, 1, 15, 1, 0, tzinfo=datetime.timezone.utc),
'precipitation_probability': 80.0,
'temperature': 12.0,
'wind_bearing': 'S',
'wind_speed': 32.7,
}),
dict({
'condition': 'clear-night',
'datetime': datetime.datetime(2020, 1, 15, 2, 0, tzinfo=datetime.timezone.utc),
'precipitation_probability': 80.0,
'temperature': 12.0,
'wind_bearing': 'S',
'wind_speed': 32.7,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecast]
dict({
'forecast': list([
dict({
'condition': 'rainy',
'datetime': datetime.datetime(2020, 1, 16, 0, 0),
'precipitation_probability': '100.0',
'temperature': 16.2,
'templow': 10.6,
'wind_bearing': 'S',
'wind_speed': 10.0,
}),
]),
})
# ---
# name: test_forecast_service[get_forecast].1
dict({
'forecast': list([
dict({
'condition': 'rainy',
'datetime': datetime.datetime(2020, 1, 15, 1, 0, tzinfo=datetime.timezone.utc),
'precipitation_probability': 80.0,
'temperature': 12.0,
'wind_bearing': 'S',
'wind_speed': 32.7,
}),
dict({
'condition': 'clear-night',
'datetime': datetime.datetime(2020, 1, 15, 2, 0, tzinfo=datetime.timezone.utc),
'precipitation_probability': 80.0,
'temperature': 12.0,
'wind_bearing': 'S',
'wind_speed': 32.7,
}),
]),
})
# ---
# name: test_forecast_service[get_forecasts]
dict({
'weather.hometown': dict({
'forecast': list([
dict({
'condition': 'rainy',
'datetime': datetime.datetime(2020, 1, 16, 0, 0),
'precipitation_probability': '100.0',
'temperature': 16.2,
'templow': 10.6,
'wind_bearing': 'S',
'wind_speed': 10.0,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecasts].1
dict({
'weather.hometown': dict({
'forecast': list([
dict({
'condition': 'rainy',
'datetime': datetime.datetime(2020, 1, 15, 1, 0, tzinfo=datetime.timezone.utc),
'precipitation_probability': 80.0,
'temperature': 12.0,
'wind_bearing': 'S',
'wind_speed': 32.7,
}),
dict({
'condition': 'clear-night',
'datetime': datetime.datetime(2020, 1, 15, 2, 0, tzinfo=datetime.timezone.utc),
'precipitation_probability': 80.0,
'temperature': 12.0,
'wind_bearing': 'S',
'wind_speed': 32.7,
}),
]),
}),
})
# ---
# name: test_forecast_subscription[daily] # name: test_forecast_subscription[daily]
list([ list([
dict({ dict({

View file

@ -22,7 +22,8 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_BEARING,
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
) )
from homeassistant.const import STATE_UNKNOWN from homeassistant.const import STATE_UNKNOWN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -152,9 +153,17 @@ async def test_failed_get_observation_forecast(hass: HomeAssistant) -> None:
assert state.attributes.get("friendly_name") == "HomeTown" assert state.attributes.get("friendly_name") == "HomeTown"
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_forecast_service( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
@ -169,7 +178,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.hometown", "entity_id": "weather.hometown",
"type": "daily", "type": "daily",
@ -181,7 +190,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.hometown", "entity_id": "weather.hometown",
"type": "hourly", "type": "hourly",

View file

@ -31,6 +31,110 @@
]), ]),
}) })
# --- # ---
# name: test_forecast_service[forecast]
dict({
'weather.somewhere': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-08T12:00:00+00:00',
'temperature': 10.0,
}),
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-09T12:00:00+00:00',
'temperature': 20.0,
}),
]),
}),
})
# ---
# name: test_forecast_service[forecast].1
dict({
'weather.somewhere': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-08T12:00:00+00:00',
'temperature': 10.0,
}),
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-09T12:00:00+00:00',
'temperature': 20.0,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecast]
dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-08T12:00:00+00:00',
'temperature': 10.0,
}),
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-09T12:00:00+00:00',
'temperature': 20.0,
}),
]),
})
# ---
# name: test_forecast_service[get_forecast].1
dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-08T12:00:00+00:00',
'temperature': 10.0,
}),
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-09T12:00:00+00:00',
'temperature': 20.0,
}),
]),
})
# ---
# name: test_forecast_service[get_forecasts]
dict({
'weather.somewhere': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-08T12:00:00+00:00',
'temperature': 10.0,
}),
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-09T12:00:00+00:00',
'temperature': 20.0,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecasts].1
dict({
'weather.somewhere': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-08T12:00:00+00:00',
'temperature': 10.0,
}),
dict({
'condition': 'lightning-rainy',
'datetime': '2023-08-09T12:00:00+00:00',
'temperature': 20.0,
}),
]),
}),
})
# ---
# name: test_forecast_subscription[daily] # name: test_forecast_subscription[daily]
list([ list([
dict({ dict({

View file

@ -9,7 +9,8 @@ from homeassistant.components.met_eireann import UPDATE_INTERVAL
from homeassistant.components.met_eireann.const import DOMAIN from homeassistant.components.met_eireann.const import DOMAIN
from homeassistant.components.weather import ( from homeassistant.components.weather import (
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -77,10 +78,18 @@ async def test_weather(hass: HomeAssistant, mock_weather) -> None:
assert len(hass.states.async_entity_ids("weather")) == 0 assert len(hass.states.async_entity_ids("weather")) == 0
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_forecast_service( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
mock_weather, mock_weather,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
mock_weather.get_forecast.return_value = [ mock_weather.get_forecast.return_value = [
@ -102,7 +111,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": entity_id, "entity_id": entity_id,
"type": "daily", "type": "daily",
@ -114,7 +123,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": entity_id, "entity_id": entity_id,
"type": "hourly", "type": "hourly",

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,8 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.metoffice.const import DEFAULT_SCAN_INTERVAL, DOMAIN from homeassistant.components.metoffice.const import DEFAULT_SCAN_INTERVAL, DOMAIN
from homeassistant.components.weather import ( from homeassistant.components.weather import (
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
) )
from homeassistant.const import STATE_UNAVAILABLE from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -421,6 +422,13 @@ async def test_legacy_config_entry(
@pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) @pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC))
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_forecast_service( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
@ -428,6 +436,7 @@ async def test_forecast_service(
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
no_sensor, no_sensor,
wavertree_data: dict[str, _Matcher], wavertree_data: dict[str, _Matcher],
service: str,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
entry = MockConfigEntry( entry = MockConfigEntry(
@ -444,7 +453,7 @@ async def test_forecast_service(
for forecast_type in ("daily", "hourly"): for forecast_type in ("daily", "hourly"):
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.met_office_wavertree_daily", "entity_id": "weather.met_office_wavertree_daily",
"type": forecast_type, "type": forecast_type,
@ -452,7 +461,6 @@ async def test_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot
# Calling the services should use cached data # Calling the services should use cached data
@ -470,7 +478,7 @@ async def test_forecast_service(
for forecast_type in ("daily", "hourly"): for forecast_type in ("daily", "hourly"):
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.met_office_wavertree_daily", "entity_id": "weather.met_office_wavertree_daily",
"type": forecast_type, "type": forecast_type,
@ -478,7 +486,6 @@ async def test_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot
# Calling the services should update the hourly forecast # Calling the services should update the hourly forecast
@ -494,7 +501,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.met_office_wavertree_daily", "entity_id": "weather.met_office_wavertree_daily",
"type": "hourly", "type": "hourly",
@ -502,7 +509,7 @@ async def test_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] == [] assert response == snapshot
@pytest.mark.parametrize( @pytest.mark.parametrize(

View file

@ -103,6 +103,309 @@
]), ]),
}) })
# --- # ---
# name: test_forecast_service[forecast]
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'is_daytime': False,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[forecast].1
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[forecast].2
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'is_daytime': False,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[forecast].3
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[forecast].4
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[forecast].5
dict({
'weather.abc_daynight': dict({
'forecast': list([
]),
}),
})
# ---
# name: test_forecast_service[get_forecast]
dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'is_daytime': False,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
})
# ---
# name: test_forecast_service[get_forecast].1
dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
})
# ---
# name: test_forecast_service[get_forecast].2
dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'is_daytime': False,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
})
# ---
# name: test_forecast_service[get_forecast].3
dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
})
# ---
# name: test_forecast_service[get_forecast].4
dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
})
# ---
# name: test_forecast_service[get_forecast].5
dict({
'forecast': list([
]),
})
# ---
# name: test_forecast_service[get_forecasts]
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'is_daytime': False,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecasts].1
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecasts].2
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'is_daytime': False,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecasts].3
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecasts].4
dict({
'weather.abc_daynight': dict({
'forecast': list([
dict({
'condition': 'lightning-rainy',
'datetime': '2019-08-12T20:00:00-04:00',
'detailed_description': 'A detailed forecast.',
'dew_point': -15.6,
'humidity': 75,
'precipitation_probability': 89,
'temperature': -12.2,
'wind_bearing': 180,
'wind_speed': 16.09,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecasts].5
dict({
'weather.abc_daynight': dict({
'forecast': list([
]),
}),
})
# ---
# name: test_forecast_subscription[hourly-weather.abc_daynight] # name: test_forecast_subscription[hourly-weather.abc_daynight]
list([ list([
dict({ dict({

View file

@ -13,7 +13,8 @@ from homeassistant.components.weather import (
ATTR_CONDITION_SUNNY, ATTR_CONDITION_SUNNY,
ATTR_FORECAST, ATTR_FORECAST,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
) )
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -400,12 +401,20 @@ async def test_legacy_config_entry(hass: HomeAssistant, no_sensor) -> None:
assert len(er.async_entries_for_config_entry(registry, entry.entry_id)) == 2 assert len(er.async_entries_for_config_entry(registry, entry.entry_id)) == 2
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_forecast_service( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
mock_simple_nws, mock_simple_nws,
no_sensor, no_sensor,
service: str,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
instance = mock_simple_nws.return_value instance = mock_simple_nws.return_value
@ -425,7 +434,7 @@ async def test_forecast_service(
for forecast_type in ("twice_daily", "hourly"): for forecast_type in ("twice_daily", "hourly"):
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.abc_daynight", "entity_id": "weather.abc_daynight",
"type": forecast_type, "type": forecast_type,
@ -433,7 +442,6 @@ async def test_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot
# Calling the services should use cached data # Calling the services should use cached data
@ -453,7 +461,7 @@ async def test_forecast_service(
for forecast_type in ("twice_daily", "hourly"): for forecast_type in ("twice_daily", "hourly"):
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.abc_daynight", "entity_id": "weather.abc_daynight",
"type": forecast_type, "type": forecast_type,
@ -461,7 +469,6 @@ async def test_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot
# Calling the services should update the hourly forecast # Calling the services should update the hourly forecast
@ -477,7 +484,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.abc_daynight", "entity_id": "weather.abc_daynight",
"type": "hourly", "type": "hourly",
@ -485,7 +492,6 @@ async def test_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot
# after additional 35 minutes data caching expires, data is no longer shown # after additional 35 minutes data caching expires, data is no longer shown
@ -495,7 +501,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.abc_daynight", "entity_id": "weather.abc_daynight",
"type": "hourly", "type": "hourly",
@ -503,7 +509,7 @@ async def test_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] == [] assert response == snapshot
@pytest.mark.parametrize( @pytest.mark.parametrize(

View file

@ -195,6 +195,418 @@
]), ]),
}) })
# --- # ---
# name: test_forecast_service[forecast]
dict({
'weather.smhi_test': 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_service[get_forecast]
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_service[get_forecasts]
dict({
'weather.smhi_test': 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 # name: test_forecast_services
dict({ dict({
'cloud_coverage': 100, 'cloud_coverage': 100,

View file

@ -20,7 +20,8 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
ATTR_WEATHER_WIND_SPEED_UNIT, ATTR_WEATHER_WIND_SPEED_UNIT,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
) )
from homeassistant.components.weather.const import ( from homeassistant.components.weather.const import (
ATTR_WEATHER_CLOUD_COVERAGE, ATTR_WEATHER_CLOUD_COVERAGE,
@ -443,11 +444,19 @@ async def test_forecast_services_lack_of_data(
assert forecast1 is None assert forecast1 is None
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_forecast_service( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
api_response: str, api_response: str,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test forecast service.""" """Test forecast service."""
uri = APIURL_TEMPLATE.format( uri = APIURL_TEMPLATE.format(
@ -463,7 +472,7 @@ async def test_forecast_service(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": ENTITY_ID, "type": "daily"}, {"entity_id": ENTITY_ID, "type": "daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,

View file

@ -1,4 +1,155 @@
# serializer version: 1 # serializer version: 1
# name: test_forecasts[config0-1-weather-forecast]
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]),
}),
})
# ---
# name: test_forecasts[config0-1-weather-forecast].1
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]),
}),
})
# ---
# name: test_forecasts[config0-1-weather-forecast].2
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'fog',
'datetime': '2023-02-17T14:00:00+00:00',
'is_daytime': True,
'temperature': 14.2,
}),
]),
}),
})
# ---
# name: test_forecasts[config0-1-weather-forecast].3
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 16.9,
}),
]),
}),
})
# ---
# name: test_forecasts[config0-1-weather-get_forecast]
dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]),
})
# ---
# name: test_forecasts[config0-1-weather-get_forecast].1
dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]),
})
# ---
# name: test_forecasts[config0-1-weather-get_forecast].2
dict({
'forecast': list([
dict({
'condition': 'fog',
'datetime': '2023-02-17T14:00:00+00:00',
'is_daytime': True,
'temperature': 14.2,
}),
]),
})
# ---
# name: test_forecasts[config0-1-weather-get_forecast].3
dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 16.9,
}),
]),
})
# ---
# name: test_forecasts[config0-1-weather-get_forecasts]
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]),
}),
})
# ---
# name: test_forecasts[config0-1-weather-get_forecasts].1
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]),
}),
})
# ---
# name: test_forecasts[config0-1-weather-get_forecasts].2
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'fog',
'datetime': '2023-02-17T14:00:00+00:00',
'is_daytime': True,
'temperature': 14.2,
}),
]),
}),
})
# ---
# name: test_forecasts[config0-1-weather-get_forecasts].3
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 16.9,
}),
]),
}),
})
# ---
# name: test_forecasts[config0-1-weather] # name: test_forecasts[config0-1-weather]
dict({ dict({
'forecast': list([ 'forecast': list([
@ -59,6 +210,138 @@
'last_wind_speed': None, 'last_wind_speed': None,
}) })
# --- # ---
# name: test_trigger_weather_services[config0-1-template-forecast]
dict({
'weather.test': dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
}),
})
# ---
# name: test_trigger_weather_services[config0-1-template-forecast].1
dict({
'weather.test': dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
}),
})
# ---
# name: test_trigger_weather_services[config0-1-template-forecast].2
dict({
'weather.test': dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'is_daytime': True,
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
}),
})
# ---
# name: test_trigger_weather_services[config0-1-template-get_forecast]
dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
})
# ---
# name: test_trigger_weather_services[config0-1-template-get_forecast].1
dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
})
# ---
# name: test_trigger_weather_services[config0-1-template-get_forecast].2
dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'is_daytime': True,
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
})
# ---
# name: test_trigger_weather_services[config0-1-template-get_forecasts]
dict({
'weather.test': dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
}),
})
# ---
# name: test_trigger_weather_services[config0-1-template-get_forecasts].1
dict({
'weather.test': dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
}),
})
# ---
# name: test_trigger_weather_services[config0-1-template-get_forecasts].2
dict({
'weather.test': dict({
'forecast': list([
dict({
'condition': 'sunny',
'datetime': '2023-10-19T06:50:05-07:00',
'is_daytime': True,
'precipitation': 20.0,
'temperature': 20.0,
'templow': 15.0,
}),
]),
}),
})
# ---
# name: test_trigger_weather_services[config0-1-template] # name: test_trigger_weather_services[config0-1-template]
dict({ dict({
'forecast': list([ 'forecast': list([

View file

@ -18,7 +18,8 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_GUST_SPEED, ATTR_WEATHER_WIND_GUST_SPEED,
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
Forecast, Forecast,
) )
from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNAVAILABLE, STATE_UNKNOWN
@ -92,6 +93,13 @@ async def test_template_state_text(hass: HomeAssistant, start_ha) -> None:
assert state.attributes.get(v_attr) == value assert state.attributes.get(v_attr) == value
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)]) @pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"config", "config",
@ -114,7 +122,7 @@ async def test_template_state_text(hass: HomeAssistant, start_ha) -> None:
], ],
) )
async def test_forecasts( async def test_forecasts(
hass: HomeAssistant, start_ha, snapshot: SnapshotAssertion hass: HomeAssistant, start_ha, snapshot: SnapshotAssertion, service: str
) -> None: ) -> None:
"""Test forecast service.""" """Test forecast service."""
for attr, _v_attr, value in [ for attr, _v_attr, value in [
@ -161,7 +169,7 @@ async def test_forecasts(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "daily"}, {"entity_id": "weather.forecast", "type": "daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,
@ -169,7 +177,7 @@ async def test_forecasts(
assert response == snapshot assert response == snapshot
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "hourly"}, {"entity_id": "weather.forecast", "type": "hourly"},
blocking=True, blocking=True,
return_response=True, return_response=True,
@ -177,7 +185,7 @@ async def test_forecasts(
assert response == snapshot assert response == snapshot
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "twice_daily"}, {"entity_id": "weather.forecast", "type": "twice_daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,
@ -204,7 +212,7 @@ async def test_forecasts(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "daily"}, {"entity_id": "weather.forecast", "type": "daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,
@ -212,6 +220,13 @@ async def test_forecasts(
assert response == snapshot assert response == snapshot
@pytest.mark.parametrize(
("service", "expected"),
[
(SERVICE_GET_FORECASTS, {"weather.forecast": {"forecast": []}}),
(LEGACY_SERVICE_GET_FORECAST, {"forecast": []}),
],
)
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)]) @pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"config", "config",
@ -236,6 +251,8 @@ async def test_forecast_invalid(
hass: HomeAssistant, hass: HomeAssistant,
start_ha, start_ha,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
service: str,
expected: dict[str, Any],
) -> None: ) -> None:
"""Test invalid forecasts.""" """Test invalid forecasts."""
for attr, _v_attr, value in [ for attr, _v_attr, value in [
@ -271,23 +288,30 @@ async def test_forecast_invalid(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "daily"}, {"entity_id": "weather.forecast", "type": "daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response == {"forecast": []} assert response == expected
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "hourly"}, {"entity_id": "weather.forecast", "type": "hourly"},
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response == {"forecast": []} assert response == expected
assert "Only valid keys in Forecast are allowed" in caplog.text assert "Only valid keys in Forecast are allowed" in caplog.text
@pytest.mark.parametrize(
("service", "expected"),
[
(SERVICE_GET_FORECASTS, {"weather.forecast": {"forecast": []}}),
(LEGACY_SERVICE_GET_FORECAST, {"forecast": []}),
],
)
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)]) @pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"config", "config",
@ -311,6 +335,8 @@ async def test_forecast_invalid_is_daytime_missing_in_twice_daily(
hass: HomeAssistant, hass: HomeAssistant,
start_ha, start_ha,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
service: str,
expected: dict[str, Any],
) -> None: ) -> None:
"""Test forecast service invalid when is_daytime missing in twice_daily forecast.""" """Test forecast service invalid when is_daytime missing in twice_daily forecast."""
for attr, _v_attr, value in [ for attr, _v_attr, value in [
@ -340,15 +366,22 @@ async def test_forecast_invalid_is_daytime_missing_in_twice_daily(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "twice_daily"}, {"entity_id": "weather.forecast", "type": "twice_daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response == {"forecast": []} assert response == expected
assert "`is_daytime` is missing in twice_daily forecast" in caplog.text assert "`is_daytime` is missing in twice_daily forecast" in caplog.text
@pytest.mark.parametrize(
("service", "expected"),
[
(SERVICE_GET_FORECASTS, {"weather.forecast": {"forecast": []}}),
(LEGACY_SERVICE_GET_FORECAST, {"forecast": []}),
],
)
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)]) @pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"config", "config",
@ -372,6 +405,8 @@ async def test_forecast_invalid_datetime_missing(
hass: HomeAssistant, hass: HomeAssistant,
start_ha, start_ha,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
service: str,
expected: dict[str, Any],
) -> None: ) -> None:
"""Test forecast service invalid when datetime missing.""" """Test forecast service invalid when datetime missing."""
for attr, _v_attr, value in [ for attr, _v_attr, value in [
@ -401,15 +436,22 @@ async def test_forecast_invalid_datetime_missing(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "twice_daily"}, {"entity_id": "weather.forecast", "type": "twice_daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response == {"forecast": []} assert response == expected
assert "`datetime` is required in forecasts" in caplog.text assert "`datetime` is required in forecasts" in caplog.text
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)]) @pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"config", "config",
@ -431,7 +473,7 @@ async def test_forecast_invalid_datetime_missing(
], ],
) )
async def test_forecast_format_error( async def test_forecast_format_error(
hass: HomeAssistant, start_ha, caplog: pytest.LogCaptureFixture hass: HomeAssistant, start_ha, caplog: pytest.LogCaptureFixture, service: str
) -> None: ) -> None:
"""Test forecast service invalid on incorrect format.""" """Test forecast service invalid on incorrect format."""
for attr, _v_attr, value in [ for attr, _v_attr, value in [
@ -467,7 +509,7 @@ async def test_forecast_format_error(
await hass.services.async_call( await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "daily"}, {"entity_id": "weather.forecast", "type": "daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,
@ -475,7 +517,7 @@ async def test_forecast_format_error(
assert "Forecasts is not a list, see Weather documentation" in caplog.text assert "Forecasts is not a list, see Weather documentation" in caplog.text
await hass.services.async_call( await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{"entity_id": "weather.forecast", "type": "hourly"}, {"entity_id": "weather.forecast", "type": "hourly"},
blocking=True, blocking=True,
return_response=True, return_response=True,
@ -638,6 +680,13 @@ async def test_trigger_action(
assert state.context is context assert state.context is context
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
@pytest.mark.parametrize(("count", "domain"), [(1, "template")]) @pytest.mark.parametrize(("count", "domain"), [(1, "template")])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"config", "config",
@ -694,6 +743,7 @@ async def test_trigger_weather_services(
start_ha, start_ha,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test trigger weather entity with services.""" """Test trigger weather entity with services."""
state = hass.states.get("weather.test") state = hass.states.get("weather.test")
@ -756,7 +806,7 @@ async def test_trigger_weather_services(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": state.entity_id, "entity_id": state.entity_id,
"type": "daily", "type": "daily",
@ -768,7 +818,7 @@ async def test_trigger_weather_services(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": state.entity_id, "entity_id": state.entity_id,
"type": "hourly", "type": "hourly",
@ -780,7 +830,7 @@ async def test_trigger_weather_services(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": state.entity_id, "entity_id": state.entity_id,
"type": "twice_daily", "type": "twice_daily",

File diff suppressed because it is too large Load diff

View file

@ -46,7 +46,8 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
ATTR_WEATHER_WIND_SPEED_UNIT, ATTR_WEATHER_WIND_SPEED_UNIT,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
) )
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY, SOURCE_USER from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY, SOURCE_USER
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME, CONF_NAME from homeassistant.const import ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME, CONF_NAME
@ -277,10 +278,18 @@ async def test_v4_weather_legacy_entities(hass: HomeAssistant) -> None:
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED_UNIT] == "km/h" assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED_UNIT] == "km/h"
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
@freeze_time(datetime(2021, 3, 6, 23, 59, 59, tzinfo=dt_util.UTC)) @freeze_time(datetime(2021, 3, 6, 23, 59, 59, tzinfo=dt_util.UTC))
async def test_v4_forecast_service( async def test_v4_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
weather_state = await _setup(hass, API_V4_ENTRY_DATA) weather_state = await _setup(hass, API_V4_ENTRY_DATA)
@ -289,7 +298,7 @@ async def test_v4_forecast_service(
for forecast_type in ("daily", "hourly"): for forecast_type in ("daily", "hourly"):
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": entity_id, "entity_id": entity_id,
"type": forecast_type, "type": forecast_type,
@ -297,10 +306,40 @@ async def test_v4_forecast_service(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot
async def test_legacy_v4_bad_forecast(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
tomorrowio_config_entry_update,
snapshot: SnapshotAssertion,
) -> None:
"""Test bad forecast data."""
freezer.move_to(datetime(2021, 3, 6, 23, 59, 59, tzinfo=dt_util.UTC))
weather_state = await _setup(hass, API_V4_ENTRY_DATA)
entity_id = weather_state.entity_id
hourly_forecast = tomorrowio_config_entry_update.return_value["forecasts"]["hourly"]
hourly_forecast[0]["values"]["precipitationProbability"] = "blah"
# Trigger data refetch
freezer.tick(timedelta(minutes=32) + timedelta(seconds=1))
await hass.async_block_till_done()
response = await hass.services.async_call(
WEATHER_DOMAIN,
LEGACY_SERVICE_GET_FORECAST,
{
"entity_id": entity_id,
"type": "hourly",
},
blocking=True,
return_response=True,
)
assert response["forecast"][0]["precipitation_probability"] is None
async def test_v4_bad_forecast( async def test_v4_bad_forecast(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
@ -321,7 +360,7 @@ async def test_v4_bad_forecast(
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, SERVICE_GET_FORECASTS,
{ {
"entity_id": entity_id, "entity_id": entity_id,
"type": "hourly", "type": "hourly",
@ -329,7 +368,12 @@ async def test_v4_bad_forecast(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"][0]["precipitation_probability"] is None assert (
response["weather.tomorrow_io_daily"]["forecast"][0][
"precipitation_probability"
]
is None
)
@pytest.mark.parametrize("forecast_type", ["daily", "hourly"]) @pytest.mark.parametrize("forecast_type", ["daily", "hourly"])

View file

@ -1,5 +1,5 @@
# serializer version: 1 # serializer version: 1
# name: test_get_forecast[daily-1] # name: test_get_forecast[daily-1-get_forecast]
dict({ dict({
'forecast': list([ 'forecast': list([
dict({ dict({
@ -12,7 +12,22 @@
]), ]),
}) })
# --- # ---
# name: test_get_forecast[hourly-2] # name: test_get_forecast[daily-1-get_forecasts]
dict({
'weather.testing': dict({
'forecast': list([
dict({
'cloud_coverage': None,
'temperature': 38.0,
'templow': 38.0,
'uv_index': None,
'wind_bearing': None,
}),
]),
}),
})
# ---
# name: test_get_forecast[hourly-2-get_forecast]
dict({ dict({
'forecast': list([ 'forecast': list([
dict({ dict({
@ -25,7 +40,22 @@
]), ]),
}) })
# --- # ---
# name: test_get_forecast[twice_daily-4] # name: test_get_forecast[hourly-2-get_forecasts]
dict({
'weather.testing': dict({
'forecast': list([
dict({
'cloud_coverage': None,
'temperature': 38.0,
'templow': 38.0,
'uv_index': None,
'wind_bearing': None,
}),
]),
}),
})
# ---
# name: test_get_forecast[twice_daily-4-get_forecast]
dict({ dict({
'forecast': list([ 'forecast': list([
dict({ dict({
@ -39,3 +69,19 @@
]), ]),
}) })
# --- # ---
# name: test_get_forecast[twice_daily-4-get_forecasts]
dict({
'weather.testing': dict({
'forecast': list([
dict({
'cloud_coverage': None,
'is_daytime': True,
'temperature': 38.0,
'templow': 38.0,
'uv_index': None,
'wind_bearing': None,
}),
]),
}),
})
# ---

View file

@ -32,8 +32,9 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
ATTR_WEATHER_WIND_SPEED_UNIT, ATTR_WEATHER_WIND_SPEED_UNIT,
DOMAIN, DOMAIN,
LEGACY_SERVICE_GET_FORECAST,
ROUNDING_PRECISION, ROUNDING_PRECISION,
SERVICE_GET_FORECAST, SERVICE_GET_FORECASTS,
Forecast, Forecast,
WeatherEntity, WeatherEntity,
WeatherEntityFeature, WeatherEntityFeature,
@ -959,6 +960,13 @@ async def test_forecast_twice_daily_missing_is_daytime(
assert msg["type"] == "result" assert msg["type"] == "result"
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
("forecast_type", "supported_features"), ("forecast_type", "supported_features"),
[ [
@ -976,6 +984,7 @@ async def test_get_forecast(
forecast_type: str, forecast_type: str,
supported_features: int, supported_features: int,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test get forecast service.""" """Test get forecast service."""
@ -1006,7 +1015,7 @@ async def test_get_forecast(
response = await hass.services.async_call( response = await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": entity0.entity_id, "entity_id": entity0.entity_id,
"type": forecast_type, "type": forecast_type,
@ -1017,9 +1026,30 @@ async def test_get_forecast(
assert response == snapshot assert response == snapshot
@pytest.mark.parametrize(
("service", "expected"),
[
(
SERVICE_GET_FORECASTS,
{
"weather.testing": {
"forecast": [],
}
},
),
(
LEGACY_SERVICE_GET_FORECAST,
{
"forecast": [],
},
),
],
)
async def test_get_forecast_no_forecast( async def test_get_forecast_no_forecast(
hass: HomeAssistant, hass: HomeAssistant,
config_flow_fixture: None, config_flow_fixture: None,
service: str,
expected: dict[str, list | dict[str, list]],
) -> None: ) -> None:
"""Test get forecast service.""" """Test get forecast service."""
@ -1040,7 +1070,7 @@ async def test_get_forecast_no_forecast(
response = await hass.services.async_call( response = await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": entity0.entity_id, "entity_id": entity0.entity_id,
"type": "daily", "type": "daily",
@ -1048,11 +1078,16 @@ async def test_get_forecast_no_forecast(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response == { assert response == expected
"forecast": [],
}
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
("supported_features", "forecast_types"), ("supported_features", "forecast_types"),
[ [
@ -1066,6 +1101,7 @@ async def test_get_forecast_unsupported(
config_flow_fixture: None, config_flow_fixture: None,
forecast_types: list[str], forecast_types: list[str],
supported_features: int, supported_features: int,
service: str,
) -> None: ) -> None:
"""Test get forecast service.""" """Test get forecast service."""
@ -1095,7 +1131,7 @@ async def test_get_forecast_unsupported(
with pytest.raises(HomeAssistantError): with pytest.raises(HomeAssistantError):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": weather_entity.entity_id, "entity_id": weather_entity.entity_id,
"type": forecast_type, "type": forecast_type,
@ -1255,3 +1291,52 @@ async def test_issue_forecast_deprecated_no_logging(
"custom_components.test_weather.weather::weather.test is using a forecast attribute on an instance of WeatherEntity" "custom_components.test_weather.weather::weather.test is using a forecast attribute on an instance of WeatherEntity"
not in caplog.text not in caplog.text
) )
async def test_issue_deprecated_service_weather_get_forecast(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
config_flow_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the issue is raised on deprecated service weather.get_forecast."""
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
async def async_forecast_daily(self) -> list[Forecast] | None:
"""Return the forecast_daily."""
return self.forecast_list
kwargs = {
"native_temperature": 38,
"native_temperature_unit": UnitOfTemperature.CELSIUS,
"supported_features": WeatherEntityFeature.FORECAST_DAILY,
}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
_ = await hass.services.async_call(
DOMAIN,
LEGACY_SERVICE_GET_FORECAST,
{
"entity_id": entity0.entity_id,
"type": "daily",
},
blocking=True,
return_response=True,
)
issue = issue_registry.async_get_issue(
"weather", "deprecated_service_weather_get_forecast"
)
assert issue
assert issue.issue_domain == "test"
assert issue.issue_id == "deprecated_service_weather_get_forecast"
assert issue.translation_key == "deprecated_service_weather_get_forecast"
assert (
"Detected use of service 'weather.get_forecast'. "
"This is deprecated and will stop working in Home Assistant 2024.6. "
"Use 'weather.get_forecasts' instead which supports multiple entities"
) in caplog.text

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
"""Weather entity tests for the WeatherKit integration.""" """Weather entity tests for the WeatherKit integration."""
import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.components.weather import ( from homeassistant.components.weather import (
@ -15,7 +16,8 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_GUST_SPEED, ATTR_WEATHER_WIND_GUST_SPEED,
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
) )
from homeassistant.components.weather.const import WeatherEntityFeature from homeassistant.components.weather.const import WeatherEntityFeature
from homeassistant.components.weatherkit.const import ATTRIBUTION from homeassistant.components.weatherkit.const import ATTRIBUTION
@ -77,15 +79,22 @@ async def test_hourly_forecast_missing(hass: HomeAssistant) -> None:
) == 0 ) == 0
@pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_hourly_forecast( async def test_hourly_forecast(
hass: HomeAssistant, snapshot: SnapshotAssertion hass: HomeAssistant, snapshot: SnapshotAssertion, service: str
) -> None: ) -> None:
"""Test states of the hourly forecast.""" """Test states of the hourly forecast."""
await init_integration(hass) await init_integration(hass)
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.home", "entity_id": "weather.home",
"type": "hourly", "type": "hourly",
@ -93,17 +102,25 @@ async def test_hourly_forecast(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot
async def test_daily_forecast(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: @pytest.mark.parametrize(
("service"),
[
SERVICE_GET_FORECASTS,
LEGACY_SERVICE_GET_FORECAST,
],
)
async def test_daily_forecast(
hass: HomeAssistant, snapshot: SnapshotAssertion, service: str
) -> None:
"""Test states of the daily forecast.""" """Test states of the daily forecast."""
await init_integration(hass) await init_integration(hass)
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
SERVICE_GET_FORECAST, service,
{ {
"entity_id": "weather.home", "entity_id": "weather.home",
"type": "daily", "type": "daily",
@ -111,5 +128,4 @@ async def test_daily_forecast(hass: HomeAssistant, snapshot: SnapshotAssertion)
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
assert response["forecast"] != []
assert response == snapshot assert response == snapshot