Files

1127 lines
35 KiB
Python

"""The tests for the Template Weather platform."""
from typing import Any
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components import template
from homeassistant.components.template.const import CONF_PICTURE, DOMAIN
from homeassistant.components.template.weather import DEFAULT_NAME
from homeassistant.components.weather import (
ATTR_WEATHER_APPARENT_TEMPERATURE,
ATTR_WEATHER_CLOUD_COVERAGE,
ATTR_WEATHER_DEW_POINT,
ATTR_WEATHER_HUMIDITY,
ATTR_WEATHER_OZONE,
ATTR_WEATHER_PRESSURE,
ATTR_WEATHER_TEMPERATURE,
ATTR_WEATHER_UV_INDEX,
ATTR_WEATHER_VISIBILITY,
ATTR_WEATHER_WIND_BEARING,
ATTR_WEATHER_WIND_GUST_SPEED,
ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
Forecast,
WeatherEntityStateAttribute,
)
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_ENTITY_PICTURE,
ATTR_ICON,
CONF_ICON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import Context, HomeAssistant, State
from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY
from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import async_setup_component
from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_attributes_template,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
make_test_trigger,
setup_entity,
setup_mock_template_entity_restore_state,
setup_restore_template_entity,
)
from tests.common import (
MockConfigEntry,
async_mock_restore_state_shutdown_restart,
mock_restore_cache_with_extra_data,
)
from tests.typing import WebSocketGenerator
ATTR_FORECAST = "forecast"
TEST_STATE_ENTITY_ID = "weather.test_state"
TEST_SENSORS = (
"sensor.apparent_temperature",
"sensor.attribution",
"sensor.cloud_coverage",
"sensor.condition",
"sensor.dew_point",
"sensor.forecast",
"sensor.forecast_daily",
"sensor.forecast_hourly",
"sensor.forecast_twice_daily",
"sensor.humidity",
"sensor.ozone",
"sensor.pressure",
"sensor.temperature",
"sensor.uv_index",
"sensor.visibility",
"sensor.wind_bearing",
"sensor.wind_gust_speed",
"sensor.wind_speed",
)
TEST_WEATHER = TemplatePlatformSetup(
WEATHER_DOMAIN,
"template_weather",
make_test_trigger(TEST_STATE_ENTITY_ID, *TEST_SENSORS),
)
TEST_LEGACY_REQUIRED = {
"condition_template": "sunny",
"temperature_template": "{{ 20 }}",
"humidity_template": "{{ 25 }}",
}
TEST_MODERN_REQUIRED = {
"condition": "sunny",
"temperature": "{{ 20 }}",
"humidity": "{{ 25 }}",
}
@pytest.fixture
async def setup_weather(
hass: HomeAssistant,
style: ConfigurationStyle,
config: dict[str, Any],
) -> None:
"""Do setup of number integration."""
await setup_entity(hass, TEST_WEATHER, style, 1, config)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
@pytest.mark.parametrize(
"config",
[
{
"condition": "{{ x - 2 }}",
"temperature": "{{ 20 }}",
"humidity": "{{ 25 }}",
},
],
)
@pytest.mark.usefixtures("setup_weather")
async def test_template_state_exception(hass: HomeAssistant) -> None:
"""Test condition produces exception."""
await async_trigger(hass, "sensor.condition", "anything")
state = hass.states.get(TEST_WEATHER.entity_id)
assert state is not None
assert state.state == STATE_UNAVAILABLE
@pytest.mark.parametrize(
("style", "config"),
[
(
ConfigurationStyle.MODERN,
{
"apparent_temperature_template": (
"{{ states('sensor.apparent_temperature') }}"
),
"attribution_template": "{{ states('sensor.attribution') }}",
"cloud_coverage_template": "{{ states('sensor.cloud_coverage') }}",
"condition_template": "{{ states('sensor.condition') }}",
"dew_point_template": "{{ states('sensor.dew_point') }}",
"humidity_template": "{{ states('sensor.humidity') | int }}",
"ozone_template": "{{ states('sensor.ozone') }}",
"pressure_template": "{{ states('sensor.pressure') }}",
"temperature_template": "{{ states('sensor.temperature') | float }}",
"unique_id": "abc123",
"uv_index_template": "{{ states('sensor.uv_index') }}",
"visibility_template": "{{ states('sensor.visibility') }}",
"wind_bearing_template": "{{ states('sensor.wind_bearing') }}",
"wind_gust_speed_template": "{{ states('sensor.wind_gust_speed') }}",
"wind_speed_template": "{{ states('sensor.wind_speed') }}",
},
),
(
ConfigurationStyle.TRIGGER,
{
"apparent_temperature_template": (
"{{ states('sensor.apparent_temperature') }}"
),
"attribution_template": "{{ states('sensor.attribution') }}",
"cloud_coverage_template": "{{ states('sensor.cloud_coverage') }}",
"condition_template": "{{ states('sensor.condition') }}",
"dew_point_template": "{{ states('sensor.dew_point') }}",
"humidity_template": "{{ states('sensor.humidity') | int }}",
"ozone_template": "{{ states('sensor.ozone') }}",
"pressure_template": "{{ states('sensor.pressure') }}",
"temperature_template": "{{ states('sensor.temperature') | float }}",
"unique_id": "abc123",
"uv_index_template": "{{ states('sensor.uv_index') }}",
"visibility_template": "{{ states('sensor.visibility') }}",
"wind_bearing_template": "{{ states('sensor.wind_bearing') }}",
"wind_gust_speed_template": "{{ states('sensor.wind_gust_speed') }}",
"wind_speed_template": "{{ states('sensor.wind_speed') }}",
},
),
(
ConfigurationStyle.MODERN,
{
"apparent_temperature": "{{ states('sensor.apparent_temperature') }}",
"attribution": "{{ states('sensor.attribution') }}",
"cloud_coverage": "{{ states('sensor.cloud_coverage') }}",
"condition": "{{ states('sensor.condition') }}",
"dew_point": "{{ states('sensor.dew_point') }}",
"humidity": "{{ states('sensor.humidity') | int }}",
"ozone": "{{ states('sensor.ozone') }}",
"pressure": "{{ states('sensor.pressure') }}",
"temperature": "{{ states('sensor.temperature') | float }}",
"unique_id": "abc123",
"uv_index": "{{ states('sensor.uv_index') }}",
"visibility": "{{ states('sensor.visibility') }}",
"wind_bearing": "{{ states('sensor.wind_bearing') }}",
"wind_gust_speed": "{{ states('sensor.wind_gust_speed') }}",
"wind_speed": "{{ states('sensor.wind_speed') }}",
},
),
(
ConfigurationStyle.TRIGGER,
{
"apparent_temperature": "{{ states('sensor.apparent_temperature') }}",
"attribution": "{{ states('sensor.attribution') }}",
"cloud_coverage": "{{ states('sensor.cloud_coverage') }}",
"condition": "{{ states('sensor.condition') }}",
"dew_point": "{{ states('sensor.dew_point') }}",
"humidity": "{{ states('sensor.humidity') }}",
"ozone": "{{ states('sensor.ozone') }}",
"pressure": "{{ states('sensor.pressure') }}",
"temperature": "{{ states('sensor.temperature') }}",
"unique_id": "abc123",
"uv_index": "{{ states('sensor.uv_index') }}",
"visibility": "{{ states('sensor.visibility') }}",
"wind_bearing": "{{ states('sensor.wind_bearing') }}",
"wind_gust_speed": "{{ states('sensor.wind_gust_speed') }}",
"wind_speed": "{{ states('sensor.wind_speed') }}",
},
),
],
)
@pytest.mark.usefixtures("setup_weather")
async def test_template_state_text(hass: HomeAssistant) -> None:
"""Test the state text of a template."""
await async_trigger(hass, "sensor.condition", "sunny")
for entity_id, v_attr, value in (
("sensor.apparent_temperature", ATTR_WEATHER_APPARENT_TEMPERATURE, 25),
("sensor.attribution", ATTR_ATTRIBUTION, "Custom"),
("sensor.cloud_coverage", ATTR_WEATHER_CLOUD_COVERAGE, 75),
("sensor.dew_point", ATTR_WEATHER_DEW_POINT, 2.2),
("sensor.humidity", ATTR_WEATHER_HUMIDITY, 60),
("sensor.ozone", ATTR_WEATHER_OZONE, 25),
("sensor.pressure", ATTR_WEATHER_PRESSURE, 1000),
("sensor.temperature", ATTR_WEATHER_TEMPERATURE, 22.3),
("sensor.uv_index", ATTR_WEATHER_UV_INDEX, 3.7),
("sensor.visibility", ATTR_WEATHER_VISIBILITY, 4.6),
("sensor.wind_bearing", ATTR_WEATHER_WIND_BEARING, 180),
("sensor.wind_gust_speed", ATTR_WEATHER_WIND_GUST_SPEED, 30),
("sensor.wind_speed", ATTR_WEATHER_WIND_SPEED, 20),
):
await async_trigger(hass, entity_id, str(value))
state = hass.states.get(TEST_WEATHER.entity_id)
assert state is not None
assert state.state == "sunny"
assert state.attributes.get(v_attr) == value
await async_trigger(hass, "sensor.condition", "None")
state = hass.states.get(TEST_WEATHER.entity_id)
assert state is not None
assert state.state == STATE_UNKNOWN
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
@pytest.mark.parametrize(
"config",
[
{
"forecast_daily_template": (
"{{ state_attr('sensor.forecast', 'forecast') }}"
),
"forecast_hourly_template": (
"{{ state_attr('sensor.forecast', 'forecast') }}"
),
"forecast_twice_daily_template": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_LEGACY_REQUIRED,
},
{
"forecast_daily": "{{ state_attr('sensor.forecast', 'forecast') }}",
"forecast_hourly": "{{ state_attr('sensor.forecast', 'forecast') }}",
"forecast_twice_daily": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_MODERN_REQUIRED,
},
],
)
@pytest.mark.usefixtures("setup_weather")
async def test_forecasts(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
"""Test forecast service."""
hass.states.async_set(
"sensor.forecast",
"sunny",
{
ATTR_FORECAST: [
Forecast(
condition="cloudy",
datetime="2023-02-17T14:00:00+00:00",
temperature=14.2,
)
]
},
)
hass.states.async_set(
"sensor.forecast_twice_daily",
"fog",
{
ATTR_FORECAST: [
Forecast(
condition="fog",
datetime="2023-02-17T14:00:00+00:00",
temperature=14.2,
is_daytime=True,
)
]
},
)
await hass.async_block_till_done()
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "daily"},
blocking=True,
return_response=True,
)
assert response == snapshot
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "hourly"},
blocking=True,
return_response=True,
)
assert response == snapshot
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "twice_daily"},
blocking=True,
return_response=True,
)
assert response == snapshot
hass.states.async_set(
"sensor.forecast",
"sunny",
{
ATTR_FORECAST: [
Forecast(
condition="cloudy",
datetime="2023-02-17T14:00:00+00:00",
temperature=16.9,
)
]
},
)
await hass.async_block_till_done()
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "daily"},
blocking=True,
return_response=True,
)
assert response == snapshot
@pytest.mark.parametrize(
("style", "config"),
[
(
ConfigurationStyle.MODERN,
{
"forecast_daily_template": (
"{{ state_attr('sensor.forecast_daily', 'forecast') }}"
),
"forecast_hourly_template": (
"{{ state_attr('sensor.forecast_hourly', 'forecast') }}"
),
"forecast_twice_daily_template": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_LEGACY_REQUIRED,
},
),
(
ConfigurationStyle.TRIGGER,
{
"forecast_daily_template": (
"{{ state_attr('sensor.forecast_daily', 'forecast') }}"
),
"forecast_hourly_template": (
"{{ state_attr('sensor.forecast_hourly', 'forecast') }}"
),
"forecast_twice_daily_template": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_LEGACY_REQUIRED,
},
),
(
ConfigurationStyle.MODERN,
{
"forecast_daily": (
"{{ state_attr('sensor.forecast_daily', 'forecast') }}"
),
"forecast_hourly": (
"{{ state_attr('sensor.forecast_hourly', 'forecast') }}"
),
"forecast_twice_daily": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_MODERN_REQUIRED,
},
),
(
ConfigurationStyle.TRIGGER,
{
"forecast_daily": (
"{{ state_attr('sensor.forecast_daily', 'forecast') }}"
),
"forecast_hourly": (
"{{ state_attr('sensor.forecast_hourly', 'forecast') }}"
),
"forecast_twice_daily": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_MODERN_REQUIRED,
},
),
],
)
@pytest.mark.usefixtures("setup_weather")
async def test_forecasts_invalid(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test invalid forecasts."""
expected = {TEST_WEATHER.entity_id: {"forecast": []}}
# Test valid keys
hass.states.async_set(
"sensor.forecast_daily",
"sunny",
{
ATTR_FORECAST: [
Forecast(
condition="cloudy",
datetime="2023-02-17T14:00:00+00:00",
temperature=14.2,
not_correct=1,
)
]
},
)
hass.states.async_set(
"sensor.forecast_hourly",
"sunny",
{ATTR_FORECAST: None},
)
await hass.async_block_till_done()
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "hourly"},
blocking=True,
return_response=True,
)
assert response == expected
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "daily"},
blocking=True,
return_response=True,
)
assert response == expected
assert "expected valid forecast keys, unallowed keys:" in caplog.text
# Test twice daily missing is_daytime
hass.states.async_set(
"sensor.forecast_twice_daily",
"sunny",
{
ATTR_FORECAST: [
Forecast(
condition="cloudy",
datetime="2023-02-17T14:00:00+00:00",
temperature=14.2,
)
]
},
)
await hass.async_block_till_done()
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "twice_daily"},
blocking=True,
return_response=True,
)
assert response == expected
assert "`is_daytime` is missing in twice_daily forecast" in caplog.text
# Test twice daily missing datetime
hass.states.async_set(
"sensor.forecast_twice_daily",
"sunny",
{
ATTR_FORECAST: [
Forecast(
condition="cloudy",
temperature=14.2,
is_daytime=True,
)
]
},
)
await hass.async_block_till_done()
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "twice_daily"},
blocking=True,
return_response=True,
)
assert response == expected
assert "`datetime` is missing in forecast" in caplog.text
@pytest.mark.parametrize(
("style", "config"),
[
(
ConfigurationStyle.MODERN,
{
"forecast_daily_template": (
"{{ state_attr('sensor.forecast_daily', 'forecast') }}"
),
"forecast_hourly_template": (
"{{ state_attr('sensor.forecast_hourly', 'forecast') }}"
),
"forecast_twice_daily_template": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_LEGACY_REQUIRED,
},
),
(
ConfigurationStyle.TRIGGER,
{
"forecast_daily_template": (
"{{ state_attr('sensor.forecast_daily', 'forecast') }}"
),
"forecast_hourly_template": (
"{{ state_attr('sensor.forecast_hourly', 'forecast') }}"
),
"forecast_twice_daily_template": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_LEGACY_REQUIRED,
},
),
(
ConfigurationStyle.MODERN,
{
"forecast_daily": (
"{{ state_attr('sensor.forecast_daily', 'forecast') }}"
),
"forecast_hourly": (
"{{ state_attr('sensor.forecast_hourly', 'forecast') }}"
),
"forecast_twice_daily": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_MODERN_REQUIRED,
},
),
(
ConfigurationStyle.TRIGGER,
{
"forecast_daily": (
"{{ state_attr('sensor.forecast_daily', 'forecast') }}"
),
"forecast_hourly": (
"{{ state_attr('sensor.forecast_hourly', 'forecast') }}"
),
"forecast_twice_daily": (
"{{ state_attr('sensor.forecast_twice_daily', 'forecast') }}"
),
**TEST_MODERN_REQUIRED,
},
),
],
)
@pytest.mark.usefixtures("setup_weather")
async def test_forecast_format_error(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test forecast service invalid on incorrect format."""
hass.states.async_set(
"sensor.forecast_daily",
"sunny",
{
ATTR_FORECAST: [
"cloudy",
"2023-02-17T14:00:00+00:00",
14.2,
1,
]
},
)
hass.states.async_set(
"sensor.forecast_hourly",
"sunny",
{
ATTR_FORECAST: {
"condition": "cloudy",
"temperature": 14.2,
"is_daytime": True,
}
},
)
await hass.async_block_till_done()
await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "daily"},
blocking=True,
return_response=True,
)
assert "expected a list of forecast dictionaries, got" in caplog.text
await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": TEST_WEATHER.entity_id, "type": "hourly"},
blocking=True,
return_response=True,
)
assert "expected a list, " in caplog.text
SAVED_EXTRA_DATA = {
"last_apparent_temperature": None,
"last_cloud_coverage": None,
"last_dew_point": None,
"last_forecast": None,
"last_humidity": 10,
"last_ozone": None,
"last_pressure": None,
"last_temperature": 20,
"last_uv_index": None,
"last_visibility": None,
"last_wind_bearing": None,
"last_wind_gust_speed": None,
"last_wind_speed": None,
}
SAVED_EXTRA_DATA_WITH_FUTURE_KEY = {
"last_apparent_temperature": None,
"last_cloud_coverage": None,
"last_dew_point": None,
"last_forecast": None,
"last_humidity": 10,
"last_ozone": None,
"last_pressure": None,
"last_temperature": 20,
"last_uv_index": None,
"last_visibility": None,
"last_wind_bearing": None,
"last_wind_gust_speed": None,
"last_wind_speed": None,
"some_key_added_in_the_future": 123,
}
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
@pytest.mark.parametrize(
"config",
[
{
"name": "test",
"condition": "{{ states('sensor.condition') }}",
"temperature": "{{ states('sensor.temperature') | float }}",
"temperature_unit": "°C",
"humidity": "{{ states('sensor.humidity') | float }}",
},
],
)
@pytest.mark.parametrize(
("saved_state", "saved_extra_data", "initial_state"),
[
("sunny", SAVED_EXTRA_DATA, "sunny"),
("sunny", SAVED_EXTRA_DATA_WITH_FUTURE_KEY, "sunny"),
(STATE_UNAVAILABLE, SAVED_EXTRA_DATA, STATE_UNKNOWN),
(STATE_UNKNOWN, SAVED_EXTRA_DATA, STATE_UNKNOWN),
],
)
async def test_restore_state(
hass: HomeAssistant,
config: ConfigType,
style: ConfigurationStyle,
saved_state: str,
saved_extra_data: dict | None,
initial_state: str,
) -> None:
"""Test restoring trigger template weather."""
restored_attributes = { # These should be ignored
"temperature": -10,
"humidity": 50,
}
setup_mock_template_entity_restore_state(
hass,
TEST_WEATHER,
saved_state,
saved_extra_data=saved_extra_data,
saved_attributes=restored_attributes,
)
await setup_restore_template_entity(
hass,
TEST_WEATHER,
style,
config,
"is_state('sensor.test_attribute', '2')",
)
state = hass.states.get(TEST_WEATHER.entity_id)
assert state.state == initial_state
state = assert_state_and_attributes(
hass,
TEST_WEATHER,
initial_state,
)
await async_trigger(hass, "sensor.condition", "cloudy")
await async_trigger(hass, "sensor.temperature", 15)
await async_trigger(hass, "sensor.humidity", 25)
state = hass.states.get(TEST_WEATHER.entity_id)
assert state.state == "cloudy"
assert state.attributes["temperature"] == 15.0
assert state.attributes["humidity"] == 25.0
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
@pytest.mark.parametrize(
"config",
[
{
"template": [
{
"unique_id": "listening-test-event",
"trigger": {"platform": "event", "event_type": "test_event"},
"action": [
{
"variables": {
"my_variable": (
"{{ trigger.event.data.temperature + 1 }}"
)
},
},
],
"weather": [
{
"name": "Hello Name",
"condition_template": "sunny",
"temperature_unit": "°C",
"humidity_template": "{{ 20 }}",
"temperature_template": "{{ my_variable + 1 }}",
}
],
},
],
},
],
)
@pytest.mark.usefixtures("start_ha")
async def test_trigger_action(hass: HomeAssistant) -> None:
"""Test trigger entity with an action works."""
state = hass.states.get("weather.hello_name")
assert state is not None
assert state.state == STATE_UNKNOWN
context = Context()
hass.bus.async_fire("test_event", {"temperature": 1}, context=context)
await hass.async_block_till_done()
state = hass.states.get("weather.hello_name")
assert state.state == "sunny"
assert state.attributes["temperature"] == 3.0
assert state.context is context
async def test_restore_weather_save_state(
hass: HomeAssistant, hass_storage: dict[str, Any], snapshot: SnapshotAssertion
) -> None:
"""Test Restore saved state for Weather trigger template."""
assert await async_setup_component(
hass,
DOMAIN,
{
"template": {
"trigger": {"platform": "event", "event_type": "test_event"},
"weather": {
"name": "test",
"condition_template": "{{ trigger.event.data.condition }}",
"temperature_template": (
"{{ trigger.event.data.temperature | float }}"
),
"temperature_unit": "°C",
"humidity_template": "{{ trigger.event.data.humidity | float }}",
},
},
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
hass.bus.async_fire(
"test_event", {"condition": "cloudy", "temperature": 15, "humidity": 25}
)
await hass.async_block_till_done()
entity = hass.states.get("weather.test")
# Trigger saving state
await async_mock_restore_state_shutdown_restart(hass)
assert len(hass_storage[RESTORE_STATE_KEY]["data"]) == 1
state = hass_storage[RESTORE_STATE_KEY]["data"][0]["state"]
assert state["entity_id"] == entity.entity_id
extra_data = hass_storage[RESTORE_STATE_KEY]["data"][0]["extra_data"]
assert extra_data == snapshot
SAVED_ATTRIBUTES_1 = {
"humidity": 20,
"temperature": 10,
}
SAVED_EXTRA_DATA_MISSING_KEY = {
"last_cloud_coverage": None,
"last_dew_point": None,
"last_humidity": 20,
"last_ozone": None,
"last_pressure": None,
"last_temperature": 20,
"last_uv_index": None,
"last_visibility": None,
"last_wind_bearing": None,
"last_wind_gust_speed": None,
"last_wind_speed": None,
}
SAVED_EXTRA_DATA_STRING_HUMIDITY = {
"last_apparent_temperature": None,
"last_cloud_coverage": None,
"last_dew_point": None,
"last_humidity": "20.0",
"last_ozone": None,
"last_pressure": None,
"last_temperature": 20.0,
"last_uv_index": None,
"last_visibility": None,
"last_wind_bearing": None,
"last_wind_gust_speed": None,
"last_wind_speed": None,
}
@pytest.mark.parametrize(
("saved_attributes", "saved_extra_data"),
[
(SAVED_ATTRIBUTES_1, SAVED_EXTRA_DATA_MISSING_KEY),
(SAVED_ATTRIBUTES_1, SAVED_EXTRA_DATA_STRING_HUMIDITY),
(SAVED_ATTRIBUTES_1, None),
],
)
async def test_trigger_entity_restore_state_fail(
hass: HomeAssistant,
saved_attributes: dict,
saved_extra_data: dict | None,
) -> None:
"""Test restoring trigger template weather fails due to missing attribute."""
saved_state = State(
"weather.test",
None,
saved_attributes,
)
mock_restore_cache_with_extra_data(hass, ((saved_state, saved_extra_data),))
assert await async_setup_component(
hass,
DOMAIN,
{
"template": {
"trigger": {"platform": "event", "event_type": "test_event"},
"weather": {
"name": "test",
"condition_template": "{{ trigger.event.data.condition }}",
"temperature_template": (
"{{ trigger.event.data.temperature | float }}"
),
"temperature_unit": "°C",
"humidity_template": "{{ trigger.event.data.humidity | float }}",
},
},
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("weather.test")
assert state.state == STATE_UNKNOWN
assert state.attributes.get("temperature") is None
@pytest.mark.parametrize(
("style", "initial_expected_state"),
[(ConfigurationStyle.MODERN, ""), (ConfigurationStyle.TRIGGER, None)],
)
@pytest.mark.parametrize(
("config", "attribute", "expected"),
[
(
{
CONF_ICON: (
"{% if states.weather.test_state.state =="
" 'sunny' %}mdi:check{% endif %}"
),
**TEST_LEGACY_REQUIRED,
},
ATTR_ICON,
"mdi:check",
),
(
{
CONF_PICTURE: (
"{% if states.weather.test_state.state =="
" 'sunny' %}check.jpg{% endif %}"
),
**TEST_LEGACY_REQUIRED,
},
ATTR_ENTITY_PICTURE,
"check.jpg",
),
],
)
@pytest.mark.usefixtures("setup_weather")
async def test_templated_optional_config(
hass: HomeAssistant,
attribute: str,
expected: str,
initial_expected_state: str | None,
) -> None:
"""Test optional config templates."""
state = hass.states.get(TEST_WEATHER.entity_id)
assert state.attributes.get(attribute) == initial_expected_state
state = hass.states.async_set(TEST_STATE_ENTITY_ID, "sunny")
await hass.async_block_till_done()
state = hass.states.get(TEST_WEATHER.entity_id)
assert state.attributes[attribute] == expected
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
) -> None:
"""Tests creating a weather from a config entry."""
hass.states.async_set(
"weather.test_state",
"sunny",
{},
)
template_config_entry = MockConfigEntry(
data={},
domain=template.DOMAIN,
options={
"name": "My template",
"condition": "{{ states('sensor.test_sensor') }}",
"humidity": "{{ 50 }}",
"temperature": "{{ 20 }}",
"template_type": WEATHER_DOMAIN,
},
title="My template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.my_template")
assert state is not None
assert state == snapshot
async def test_flow_preview(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the config flow preview."""
state = await async_get_flow_preview_state(
hass,
hass_ws_client,
WEATHER_DOMAIN,
{
"name": "My template",
"condition": "{{ 'sunny' }}",
"humidity": "{{ 50 }}",
"temperature": "{{ 20 }}",
},
)
assert state["state"] == "sunny"
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass,
TEST_WEATHER,
style,
TEST_MODERN_REQUIRED,
)
@pytest.mark.parametrize(
"attribute",
list(WeatherEntityStateAttribute),
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_WEATHER,
style,
0,
{
**TEST_MODERN_REQUIRED,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_attributes_template(
hass: HomeAssistant,
style: ConfigurationStyle,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test attributes as a single template."""
await assert_attributes_template(
hass,
TEST_WEATHER,
style,
TEST_MODERN_REQUIRED,
caplog,
)
@pytest.mark.parametrize(
"attribute",
list(WeatherEntityStateAttribute),
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_attributes_template_with_blocked_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute: WeatherEntityStateAttribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked attributes for a single attributes template."""
await setup_entity(
hass,
TEST_WEATHER,
style,
1,
{
**TEST_MODERN_REQUIRED,
"attributes": f"{{{{ dict({attribute}='does not matter') }}}}",
},
)
await async_trigger(hass, "sensor.test_extra_attributes", "anything")
error = f"Unsupported attribute(s) found for {TEST_WEATHER.entity_id}: {attribute}"
assert error in caplog.text