mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
2023.9.1 (#99950)
This commit is contained in:
commit
9e6ac1d7ec
@ -416,7 +416,6 @@ omit =
|
||||
homeassistant/components/freebox/device_tracker.py
|
||||
homeassistant/components/freebox/home_base.py
|
||||
homeassistant/components/freebox/router.py
|
||||
homeassistant/components/freebox/sensor.py
|
||||
homeassistant/components/freebox/switch.py
|
||||
homeassistant/components/fritz/common.py
|
||||
homeassistant/components/fritz/device_tracker.py
|
||||
|
@ -90,6 +90,13 @@ class AlexaUnsupportedThermostatModeError(AlexaError):
|
||||
error_type = "UNSUPPORTED_THERMOSTAT_MODE"
|
||||
|
||||
|
||||
class AlexaUnsupportedThermostatTargetStateError(AlexaError):
|
||||
"""Class to represent unsupported climate target state error."""
|
||||
|
||||
namespace = "Alexa.ThermostatController"
|
||||
error_type = "INVALID_TARGET_STATE"
|
||||
|
||||
|
||||
class AlexaTempRangeError(AlexaError):
|
||||
"""Class to represent TempRange errors."""
|
||||
|
||||
|
@ -73,6 +73,7 @@ from .errors import (
|
||||
AlexaSecurityPanelAuthorizationRequired,
|
||||
AlexaTempRangeError,
|
||||
AlexaUnsupportedThermostatModeError,
|
||||
AlexaUnsupportedThermostatTargetStateError,
|
||||
AlexaVideoActionNotPermittedForContentError,
|
||||
)
|
||||
from .state_report import AlexaDirective, AlexaResponse, async_enable_proactive_mode
|
||||
@ -911,7 +912,13 @@ async def async_api_adjust_target_temp(
|
||||
}
|
||||
)
|
||||
else:
|
||||
target_temp = float(entity.attributes[ATTR_TEMPERATURE]) + temp_delta
|
||||
current_target_temp: str | None = entity.attributes.get(ATTR_TEMPERATURE)
|
||||
if current_target_temp is None:
|
||||
raise AlexaUnsupportedThermostatTargetStateError(
|
||||
"The current target temperature is not set, "
|
||||
"cannot adjust target temperature"
|
||||
)
|
||||
target_temp = float(current_target_temp) + temp_delta
|
||||
|
||||
if target_temp < min_temp or target_temp > max_temp:
|
||||
raise AlexaTempRangeError(hass, target_temp, min_temp, max_temp)
|
||||
|
@ -28,7 +28,13 @@ from homeassistant.const import (
|
||||
)
|
||||
import homeassistant.core as ha
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceNotFound, TemplateError, Unauthorized
|
||||
from homeassistant.exceptions import (
|
||||
InvalidEntityFormatError,
|
||||
InvalidStateError,
|
||||
ServiceNotFound,
|
||||
TemplateError,
|
||||
Unauthorized,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv, template
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.service import async_get_all_descriptions
|
||||
@ -222,7 +228,7 @@ class APIEntityStateView(HomeAssistantView):
|
||||
"""Update state of entity."""
|
||||
if not request["hass_user"].is_admin:
|
||||
raise Unauthorized(entity_id=entity_id)
|
||||
hass = request.app["hass"]
|
||||
hass: HomeAssistant = request.app["hass"]
|
||||
try:
|
||||
data = await request.json()
|
||||
except ValueError:
|
||||
@ -237,9 +243,16 @@ class APIEntityStateView(HomeAssistantView):
|
||||
is_new_state = hass.states.get(entity_id) is None
|
||||
|
||||
# Write state
|
||||
hass.states.async_set(
|
||||
entity_id, new_state, attributes, force_update, self.context(request)
|
||||
)
|
||||
try:
|
||||
hass.states.async_set(
|
||||
entity_id, new_state, attributes, force_update, self.context(request)
|
||||
)
|
||||
except InvalidEntityFormatError:
|
||||
return self.json_message(
|
||||
"Invalid entity ID specified.", HTTPStatus.BAD_REQUEST
|
||||
)
|
||||
except InvalidStateError:
|
||||
return self.json_message("Invalid state specified.", HTTPStatus.BAD_REQUEST)
|
||||
|
||||
# Read the state back for our response
|
||||
status_code = HTTPStatus.CREATED if is_new_state else HTTPStatus.OK
|
||||
|
@ -15,10 +15,10 @@
|
||||
"quality_scale": "internal",
|
||||
"requirements": [
|
||||
"bleak==0.21.0",
|
||||
"bleak-retry-connector==3.1.2",
|
||||
"bluetooth-adapters==0.16.0",
|
||||
"bluetooth-auto-recovery==1.2.1",
|
||||
"bleak-retry-connector==3.1.3",
|
||||
"bluetooth-adapters==0.16.1",
|
||||
"bluetooth-auto-recovery==1.2.2",
|
||||
"bluetooth-data-tools==1.11.0",
|
||||
"dbus-fast==1.94.1"
|
||||
"dbus-fast==1.95.2"
|
||||
]
|
||||
}
|
||||
|
@ -518,6 +518,8 @@ class ElkEntity(Entity):
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Device info connecting via the ElkM1 system."""
|
||||
return DeviceInfo(
|
||||
name=self._element.name,
|
||||
identifiers={(DOMAIN, self._unique_id)},
|
||||
via_device=(DOMAIN, f"{self._prefix}_system"),
|
||||
)
|
||||
|
||||
|
@ -6,5 +6,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["sense_energy"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["sense_energy==0.12.0"]
|
||||
"requirements": ["sense_energy==0.12.1"]
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"requirements": ["pyenphase==1.9.1"],
|
||||
"requirements": ["pyenphase==1.11.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/epson",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["epson_projector"],
|
||||
"requirements": ["epson-projector==0.5.0"]
|
||||
"requirements": ["epson-projector==0.5.1"]
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import logging
|
||||
from epson_projector import Projector, ProjectorUnavailableError
|
||||
from epson_projector.const import (
|
||||
BACK,
|
||||
BUSY,
|
||||
BUSY_CODES,
|
||||
CMODE,
|
||||
CMODE_LIST,
|
||||
CMODE_LIST_SET,
|
||||
@ -147,7 +147,7 @@ class EpsonProjectorMediaPlayer(MediaPlayerEntity):
|
||||
self._attr_volume_level = float(volume)
|
||||
except ValueError:
|
||||
self._attr_volume_level = None
|
||||
elif power_state == BUSY:
|
||||
elif power_state in BUSY_CODES:
|
||||
self._attr_state = MediaPlayerState.ON
|
||||
else:
|
||||
self._attr_state = MediaPlayerState.OFF
|
||||
|
@ -85,4 +85,7 @@ CATEGORY_TO_MODEL = {
|
||||
|
||||
HOME_COMPATIBLE_CATEGORIES = [
|
||||
FreeboxHomeCategory.CAMERA,
|
||||
FreeboxHomeCategory.DWS,
|
||||
FreeboxHomeCategory.KFB,
|
||||
FreeboxHomeCategory.PIR,
|
||||
]
|
||||
|
@ -156,7 +156,12 @@ class FreeboxRouter:
|
||||
fbx_disks: list[dict[str, Any]] = await self._api.storage.get_disks() or []
|
||||
|
||||
for fbx_disk in fbx_disks:
|
||||
self.disks[fbx_disk["id"]] = fbx_disk
|
||||
disk: dict[str, Any] = {**fbx_disk}
|
||||
disk_part: dict[int, dict[str, Any]] = {}
|
||||
for fbx_disk_part in fbx_disk["partitions"]:
|
||||
disk_part[fbx_disk_part["id"]] = fbx_disk_part
|
||||
disk["partitions"] = disk_part
|
||||
self.disks[fbx_disk["id"]] = disk
|
||||
|
||||
async def _update_raids_sensors(self) -> None:
|
||||
"""Update Freebox raids."""
|
||||
|
@ -95,7 +95,7 @@ async def async_setup_entry(
|
||||
entities.extend(
|
||||
FreeboxDiskSensor(router, disk, partition, description)
|
||||
for disk in router.disks.values()
|
||||
for partition in disk["partitions"]
|
||||
for partition in disk["partitions"].values()
|
||||
for description in DISK_PARTITION_SENSORS
|
||||
)
|
||||
|
||||
@ -197,7 +197,8 @@ class FreeboxDiskSensor(FreeboxSensor):
|
||||
) -> None:
|
||||
"""Initialize a Freebox disk sensor."""
|
||||
super().__init__(router, description)
|
||||
self._partition = partition
|
||||
self._disk_id = disk["id"]
|
||||
self._partition_id = partition["id"]
|
||||
self._attr_name = f"{partition['label']} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{router.mac} {description.key} {disk['id']} {partition['id']}"
|
||||
@ -218,10 +219,10 @@ class FreeboxDiskSensor(FreeboxSensor):
|
||||
def async_update_state(self) -> None:
|
||||
"""Update the Freebox disk sensor."""
|
||||
value = None
|
||||
if self._partition.get("total_bytes"):
|
||||
value = round(
|
||||
self._partition["free_bytes"] * 100 / self._partition["total_bytes"], 2
|
||||
)
|
||||
disk: dict[str, Any] = self._router.disks[self._disk_id]
|
||||
partition: dict[str, Any] = disk["partitions"][self._partition_id]
|
||||
if partition.get("total_bytes"):
|
||||
value = round(partition["free_bytes"] * 100 / partition["total_bytes"], 2)
|
||||
self._attr_native_value = value
|
||||
|
||||
|
||||
|
@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20230906.1"]
|
||||
"requirements": ["home-assistant-frontend==20230908.0"]
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity):
|
||||
"""Get the latest data and updates the state."""
|
||||
LOGGER.debug("Updating Hydrawise binary sensor: %s", self.name)
|
||||
if self.entity_description.key == "status":
|
||||
self._attr_is_on = self.coordinator.api.status == "All good!"
|
||||
self._attr_is_on = self.coordinator.last_update_success
|
||||
elif self.entity_description.key == "is_watering":
|
||||
relay_data = self.coordinator.api.relays_by_zone_number[self.data["relay"]]
|
||||
self._attr_is_on = relay_data["timestr"] == "Now"
|
||||
|
@ -33,7 +33,7 @@ async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> boo
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=coordinator.serial_number,
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, entry.entry_id)},
|
||||
manufacturer="Livisi",
|
||||
name=f"SHC {coordinator.controller_type} {coordinator.serial_number}",
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pylutron_caseta"],
|
||||
"requirements": ["pylutron-caseta==0.18.1"],
|
||||
"requirements": ["pylutron-caseta==0.18.2"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_lutron._tcp.local.",
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/mill",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["mill", "mill_local"],
|
||||
"requirements": ["millheater==0.11.1", "mill-local==0.2.0"]
|
||||
"requirements": ["millheater==0.11.2", "mill-local==0.2.0"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pymodbus"],
|
||||
"quality_scale": "gold",
|
||||
"requirements": ["pymodbus==3.4.1"]
|
||||
"requirements": ["pymodbus==3.5.1"]
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity):
|
||||
"Empty template output for entity: %s with state topic: %s."
|
||||
" Payload: '%s', with value template '%s'"
|
||||
),
|
||||
self._config[CONF_NAME],
|
||||
self.entity_id,
|
||||
self._config[CONF_STATE_TOPIC],
|
||||
msg.payload,
|
||||
self._config.get(CONF_VALUE_TEMPLATE),
|
||||
@ -240,7 +240,7 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity):
|
||||
"No matching payload found for entity: %s with state topic: %s."
|
||||
" Payload: '%s'%s"
|
||||
),
|
||||
self._config[CONF_NAME],
|
||||
self.entity_id,
|
||||
self._config[CONF_STATE_TOPIC],
|
||||
msg.payload,
|
||||
template_info,
|
||||
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, Literal, TypedDict
|
||||
|
||||
import noaa_coops as coops
|
||||
import requests
|
||||
@ -17,6 +18,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pandas import Timestamp
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_STATION_ID = "station_id"
|
||||
@ -76,40 +80,56 @@ def setup_platform(
|
||||
add_entities([noaa_sensor], True)
|
||||
|
||||
|
||||
class NOAATidesData(TypedDict):
|
||||
"""Representation of a single tide."""
|
||||
|
||||
time_stamp: list[Timestamp]
|
||||
hi_lo: list[Literal["L"] | Literal["H"]]
|
||||
predicted_wl: list[float]
|
||||
|
||||
|
||||
class NOAATidesAndCurrentsSensor(SensorEntity):
|
||||
"""Representation of a NOAA Tides and Currents sensor."""
|
||||
|
||||
_attr_attribution = "Data provided by NOAA"
|
||||
|
||||
def __init__(self, name, station_id, timezone, unit_system, station):
|
||||
def __init__(self, name, station_id, timezone, unit_system, station) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self._name = name
|
||||
self._station_id = station_id
|
||||
self._timezone = timezone
|
||||
self._unit_system = unit_system
|
||||
self._station = station
|
||||
self.data = None
|
||||
self.data: NOAATidesData | None = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of this device."""
|
||||
attr = {}
|
||||
attr: dict[str, Any] = {}
|
||||
if self.data is None:
|
||||
return attr
|
||||
if self.data["hi_lo"][1] == "H":
|
||||
attr["high_tide_time"] = self.data.index[1].strftime("%Y-%m-%dT%H:%M")
|
||||
attr["high_tide_time"] = self.data["time_stamp"][1].strftime(
|
||||
"%Y-%m-%dT%H:%M"
|
||||
)
|
||||
attr["high_tide_height"] = self.data["predicted_wl"][1]
|
||||
attr["low_tide_time"] = self.data.index[2].strftime("%Y-%m-%dT%H:%M")
|
||||
attr["low_tide_time"] = self.data["time_stamp"][2].strftime(
|
||||
"%Y-%m-%dT%H:%M"
|
||||
)
|
||||
attr["low_tide_height"] = self.data["predicted_wl"][2]
|
||||
elif self.data["hi_lo"][1] == "L":
|
||||
attr["low_tide_time"] = self.data.index[1].strftime("%Y-%m-%dT%H:%M")
|
||||
attr["low_tide_time"] = self.data["time_stamp"][1].strftime(
|
||||
"%Y-%m-%dT%H:%M"
|
||||
)
|
||||
attr["low_tide_height"] = self.data["predicted_wl"][1]
|
||||
attr["high_tide_time"] = self.data.index[2].strftime("%Y-%m-%dT%H:%M")
|
||||
attr["high_tide_time"] = self.data["time_stamp"][2].strftime(
|
||||
"%Y-%m-%dT%H:%M"
|
||||
)
|
||||
attr["high_tide_height"] = self.data["predicted_wl"][2]
|
||||
return attr
|
||||
|
||||
@ -118,7 +138,7 @@ class NOAATidesAndCurrentsSensor(SensorEntity):
|
||||
"""Return the state of the device."""
|
||||
if self.data is None:
|
||||
return None
|
||||
api_time = self.data.index[0]
|
||||
api_time = self.data["time_stamp"][0]
|
||||
if self.data["hi_lo"][0] == "H":
|
||||
tidetime = api_time.strftime("%-I:%M %p")
|
||||
return f"High tide at {tidetime}"
|
||||
@ -142,8 +162,13 @@ class NOAATidesAndCurrentsSensor(SensorEntity):
|
||||
units=self._unit_system,
|
||||
time_zone=self._timezone,
|
||||
)
|
||||
self.data = df_predictions.head()
|
||||
_LOGGER.debug("Data = %s", self.data)
|
||||
api_data = df_predictions.head()
|
||||
self.data = NOAATidesData(
|
||||
time_stamp=list(api_data.index),
|
||||
hi_lo=list(api_data["hi_lo"].values),
|
||||
predicted_wl=list(api_data["predicted_wl"].values),
|
||||
)
|
||||
_LOGGER.debug("Data = %s", api_data)
|
||||
_LOGGER.debug(
|
||||
"Recent Tide data queried with start time set to %s",
|
||||
begin.strftime("%m-%d-%Y %H:%M"),
|
||||
|
@ -13,7 +13,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
|
||||
"requirements": ["pyoverkiz==1.10.1"],
|
||||
"requirements": ["pyoverkiz==1.9.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_kizbox._tcp.local.",
|
||||
|
@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aiorecollect"],
|
||||
"requirements": ["aiorecollect==1.0.8"]
|
||||
"requirements": ["aiorecollect==2023.09.0"]
|
||||
}
|
||||
|
@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/sense",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["sense_energy"],
|
||||
"requirements": ["sense-energy==0.12.0"]
|
||||
"requirements": ["sense-energy==0.12.1"]
|
||||
}
|
||||
|
@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["hatasmota"],
|
||||
"mqtt": ["tasmota/discovery/#"],
|
||||
"requirements": ["HATasmota==0.7.0"]
|
||||
"requirements": ["HATasmota==0.7.1"]
|
||||
}
|
||||
|
@ -302,6 +302,8 @@ class TomorrowioDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
[
|
||||
TMRW_ATTR_TEMPERATURE_LOW,
|
||||
TMRW_ATTR_TEMPERATURE_HIGH,
|
||||
TMRW_ATTR_DEW_POINT,
|
||||
TMRW_ATTR_HUMIDITY,
|
||||
TMRW_ATTR_WIND_SPEED,
|
||||
TMRW_ATTR_WIND_DIRECTION,
|
||||
TMRW_ATTR_CONDITION,
|
||||
|
@ -122,7 +122,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if same_hub_entries:
|
||||
await asyncio.wait(
|
||||
[
|
||||
self.hass.config_entries.async_remove(entry_id)
|
||||
asyncio.create_task(self.hass.config_entries.async_remove(entry_id))
|
||||
for entry_id in same_hub_entries
|
||||
]
|
||||
)
|
||||
|
@ -112,9 +112,9 @@ class VodafoneStationRouter(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
dev_info, utc_point_in_time
|
||||
),
|
||||
)
|
||||
for dev_info in (await self.api.get_all_devices()).values()
|
||||
for dev_info in (await self.api.get_devices_data()).values()
|
||||
}
|
||||
data_sensors = await self.api.get_user_data()
|
||||
data_sensors = await self.api.get_sensor_data()
|
||||
await self.api.logout()
|
||||
return UpdateCoordinatorDataType(data_devices, data_sensors)
|
||||
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/vodafone_station",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiovodafone"],
|
||||
"requirements": ["aiovodafone==0.0.6"]
|
||||
"requirements": ["aiovodafone==0.1.0"]
|
||||
}
|
||||
|
@ -542,9 +542,8 @@ async def handle_render_template(
|
||||
timed_out = await template_obj.async_render_will_timeout(
|
||||
timeout, variables, strict=msg["strict"], log_fn=log_fn
|
||||
)
|
||||
except TemplateError as ex:
|
||||
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
|
||||
return
|
||||
except TemplateError:
|
||||
timed_out = False
|
||||
|
||||
if timed_out:
|
||||
connection.send_error(
|
||||
@ -565,7 +564,9 @@ async def handle_render_template(
|
||||
if not report_errors:
|
||||
return
|
||||
connection.send_message(
|
||||
messages.event_message(msg["id"], {"error": str(result)})
|
||||
messages.event_message(
|
||||
msg["id"], {"error": str(result), "level": "ERROR"}
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
@ -581,7 +582,6 @@ async def handle_render_template(
|
||||
hass,
|
||||
[TrackTemplate(template_obj, variables)],
|
||||
_template_listener,
|
||||
raise_on_template_error=True,
|
||||
strict=msg["strict"],
|
||||
log_fn=log_fn,
|
||||
)
|
||||
|
@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["zeroconf"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["zeroconf==0.91.1"]
|
||||
"requirements": ["zeroconf==0.98.0"]
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
"universal_silabs_flasher"
|
||||
],
|
||||
"requirements": [
|
||||
"bellows==0.36.2",
|
||||
"bellows==0.36.3",
|
||||
"pyserial==3.5",
|
||||
"pyserial-asyncio==0.6",
|
||||
"zha-quirks==0.0.103",
|
||||
|
@ -7,7 +7,7 @@ from typing import Final
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
MINOR_VERSION: Final = 9
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||
|
@ -917,7 +917,6 @@ class TrackTemplateResultInfo:
|
||||
|
||||
def async_setup(
|
||||
self,
|
||||
raise_on_template_error: bool,
|
||||
strict: bool = False,
|
||||
log_fn: Callable[[int, str], None] | None = None,
|
||||
) -> None:
|
||||
@ -955,8 +954,6 @@ class TrackTemplateResultInfo:
|
||||
)
|
||||
|
||||
if info.exception:
|
||||
if raise_on_template_error:
|
||||
raise info.exception
|
||||
if not log_fn:
|
||||
_LOGGER.error(
|
||||
"Error while processing template: %s",
|
||||
@ -1239,7 +1236,6 @@ def async_track_template_result(
|
||||
hass: HomeAssistant,
|
||||
track_templates: Sequence[TrackTemplate],
|
||||
action: TrackTemplateResultListener,
|
||||
raise_on_template_error: bool = False,
|
||||
strict: bool = False,
|
||||
log_fn: Callable[[int, str], None] | None = None,
|
||||
has_super_template: bool = False,
|
||||
@ -1266,11 +1262,6 @@ def async_track_template_result(
|
||||
An iterable of TrackTemplate.
|
||||
action
|
||||
Callable to call with results.
|
||||
raise_on_template_error
|
||||
When set to True, if there is an exception
|
||||
processing the template during setup, the system
|
||||
will raise the exception instead of setting up
|
||||
tracking.
|
||||
strict
|
||||
When set to True, raise on undefined variables.
|
||||
log_fn
|
||||
@ -1286,7 +1277,7 @@ def async_track_template_result(
|
||||
|
||||
"""
|
||||
tracker = TrackTemplateResultInfo(hass, track_templates, action, has_super_template)
|
||||
tracker.async_setup(raise_on_template_error, strict=strict, log_fn=log_fn)
|
||||
tracker.async_setup(strict=strict, log_fn=log_fn)
|
||||
return tracker
|
||||
|
||||
|
||||
|
@ -8,21 +8,21 @@ atomicwrites-homeassistant==1.4.1
|
||||
attrs==23.1.0
|
||||
awesomeversion==22.9.0
|
||||
bcrypt==4.0.1
|
||||
bleak-retry-connector==3.1.2
|
||||
bleak-retry-connector==3.1.3
|
||||
bleak==0.21.0
|
||||
bluetooth-adapters==0.16.0
|
||||
bluetooth-auto-recovery==1.2.1
|
||||
bluetooth-adapters==0.16.1
|
||||
bluetooth-auto-recovery==1.2.2
|
||||
bluetooth-data-tools==1.11.0
|
||||
certifi>=2021.5.30
|
||||
ciso8601==2.3.0
|
||||
cryptography==41.0.3
|
||||
dbus-fast==1.94.1
|
||||
dbus-fast==1.95.2
|
||||
fnv-hash-fast==0.4.1
|
||||
ha-av==10.1.1
|
||||
hass-nabucasa==0.70.0
|
||||
hassil==1.2.5
|
||||
home-assistant-bluetooth==1.10.3
|
||||
home-assistant-frontend==20230906.1
|
||||
home-assistant-frontend==20230908.0
|
||||
home-assistant-intents==2023.8.2
|
||||
httpx==0.24.1
|
||||
ifaddr==0.2.0
|
||||
@ -53,7 +53,7 @@ voluptuous-serialize==2.6.0
|
||||
voluptuous==0.13.1
|
||||
webrtcvad==2.0.10
|
||||
yarl==1.9.2
|
||||
zeroconf==0.91.1
|
||||
zeroconf==0.98.0
|
||||
|
||||
# Constrain pycryptodome to avoid vulnerability
|
||||
# see https://github.com/home-assistant/core/pull/16238
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.9.0"
|
||||
version = "2023.9.1"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -29,7 +29,7 @@ DoorBirdPy==2.1.0
|
||||
HAP-python==4.7.1
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
HATasmota==0.7.0
|
||||
HATasmota==0.7.1
|
||||
|
||||
# homeassistant.components.mastodon
|
||||
Mastodon.py==1.5.1
|
||||
@ -327,7 +327,7 @@ aiopyarr==23.4.0
|
||||
aioqsw==0.3.4
|
||||
|
||||
# homeassistant.components.recollect_waste
|
||||
aiorecollect==1.0.8
|
||||
aiorecollect==2023.09.0
|
||||
|
||||
# homeassistant.components.ridwell
|
||||
aioridwell==2023.07.0
|
||||
@ -369,7 +369,7 @@ aiounifi==61
|
||||
aiovlc==0.1.0
|
||||
|
||||
# homeassistant.components.vodafone_station
|
||||
aiovodafone==0.0.6
|
||||
aiovodafone==0.1.0
|
||||
|
||||
# homeassistant.components.waqi
|
||||
aiowaqi==0.2.1
|
||||
@ -509,7 +509,7 @@ beautifulsoup4==4.12.2
|
||||
# beewi-smartclim==0.0.10
|
||||
|
||||
# homeassistant.components.zha
|
||||
bellows==0.36.2
|
||||
bellows==0.36.3
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer-connected==0.14.0
|
||||
@ -518,7 +518,7 @@ bimmer-connected==0.14.0
|
||||
bizkaibus==0.1.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak-retry-connector==3.1.2
|
||||
bleak-retry-connector==3.1.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak==0.21.0
|
||||
@ -540,10 +540,10 @@ bluemaestro-ble==0.2.3
|
||||
# bluepy==1.3.0
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-adapters==0.16.0
|
||||
bluetooth-adapters==0.16.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-auto-recovery==1.2.1
|
||||
bluetooth-auto-recovery==1.2.2
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
# homeassistant.components.esphome
|
||||
@ -641,7 +641,7 @@ datadog==0.15.0
|
||||
datapoint==0.9.8
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
dbus-fast==1.94.1
|
||||
dbus-fast==1.95.2
|
||||
|
||||
# homeassistant.components.debugpy
|
||||
debugpy==1.6.7
|
||||
@ -751,7 +751,7 @@ env-canada==0.5.36
|
||||
ephem==4.1.2
|
||||
|
||||
# homeassistant.components.epson
|
||||
epson-projector==0.5.0
|
||||
epson-projector==0.5.1
|
||||
|
||||
# homeassistant.components.epsonworkforce
|
||||
epsonprinter==0.0.9
|
||||
@ -994,7 +994,7 @@ hole==0.8.0
|
||||
holidays==0.28
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20230906.1
|
||||
home-assistant-frontend==20230908.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.8.2
|
||||
@ -1213,7 +1213,7 @@ micloud==0.5
|
||||
mill-local==0.2.0
|
||||
|
||||
# homeassistant.components.mill
|
||||
millheater==0.11.1
|
||||
millheater==0.11.2
|
||||
|
||||
# homeassistant.components.minio
|
||||
minio==7.1.12
|
||||
@ -1671,7 +1671,7 @@ pyedimax==0.2.1
|
||||
pyefergy==22.1.1
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==1.9.1
|
||||
pyenphase==1.11.0
|
||||
|
||||
# homeassistant.components.envisalink
|
||||
pyenvisalink==4.6
|
||||
@ -1821,7 +1821,7 @@ pylitejet==0.5.0
|
||||
pylitterbot==2023.4.5
|
||||
|
||||
# homeassistant.components.lutron_caseta
|
||||
pylutron-caseta==0.18.1
|
||||
pylutron-caseta==0.18.2
|
||||
|
||||
# homeassistant.components.lutron
|
||||
pylutron==0.2.8
|
||||
@ -1851,7 +1851,7 @@ pymitv==1.4.3
|
||||
pymochad==0.2.0
|
||||
|
||||
# homeassistant.components.modbus
|
||||
pymodbus==3.4.1
|
||||
pymodbus==3.5.1
|
||||
|
||||
# homeassistant.components.monoprice
|
||||
pymonoprice==0.4
|
||||
@ -1916,7 +1916,7 @@ pyotgw==2.1.3
|
||||
pyotp==2.8.0
|
||||
|
||||
# homeassistant.components.overkiz
|
||||
pyoverkiz==1.10.1
|
||||
pyoverkiz==1.9.0
|
||||
|
||||
# homeassistant.components.openweathermap
|
||||
pyowm==3.2.0
|
||||
@ -2369,10 +2369,10 @@ securetar==2023.3.0
|
||||
sendgrid==6.8.2
|
||||
|
||||
# homeassistant.components.sense
|
||||
sense-energy==0.12.0
|
||||
sense-energy==0.12.1
|
||||
|
||||
# homeassistant.components.emulated_kasa
|
||||
sense_energy==0.12.0
|
||||
sense_energy==0.12.1
|
||||
|
||||
# homeassistant.components.sensirion_ble
|
||||
sensirion-ble==0.1.0
|
||||
@ -2766,7 +2766,7 @@ zamg==0.3.0
|
||||
zengge==0.2
|
||||
|
||||
# homeassistant.components.zeroconf
|
||||
zeroconf==0.91.1
|
||||
zeroconf==0.98.0
|
||||
|
||||
# homeassistant.components.zeversolar
|
||||
zeversolar==0.3.1
|
||||
|
@ -28,7 +28,7 @@ DoorBirdPy==2.1.0
|
||||
HAP-python==4.7.1
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
HATasmota==0.7.0
|
||||
HATasmota==0.7.1
|
||||
|
||||
# homeassistant.components.doods
|
||||
# homeassistant.components.generic
|
||||
@ -302,7 +302,7 @@ aiopyarr==23.4.0
|
||||
aioqsw==0.3.4
|
||||
|
||||
# homeassistant.components.recollect_waste
|
||||
aiorecollect==1.0.8
|
||||
aiorecollect==2023.09.0
|
||||
|
||||
# homeassistant.components.ridwell
|
||||
aioridwell==2023.07.0
|
||||
@ -344,7 +344,7 @@ aiounifi==61
|
||||
aiovlc==0.1.0
|
||||
|
||||
# homeassistant.components.vodafone_station
|
||||
aiovodafone==0.0.6
|
||||
aiovodafone==0.1.0
|
||||
|
||||
# homeassistant.components.watttime
|
||||
aiowatttime==0.1.1
|
||||
@ -430,13 +430,13 @@ base36==0.1.1
|
||||
beautifulsoup4==4.12.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
bellows==0.36.2
|
||||
bellows==0.36.3
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer-connected==0.14.0
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak-retry-connector==3.1.2
|
||||
bleak-retry-connector==3.1.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak==0.21.0
|
||||
@ -451,10 +451,10 @@ blinkpy==0.21.0
|
||||
bluemaestro-ble==0.2.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-adapters==0.16.0
|
||||
bluetooth-adapters==0.16.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-auto-recovery==1.2.1
|
||||
bluetooth-auto-recovery==1.2.2
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
# homeassistant.components.esphome
|
||||
@ -521,7 +521,7 @@ datadog==0.15.0
|
||||
datapoint==0.9.8
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
dbus-fast==1.94.1
|
||||
dbus-fast==1.95.2
|
||||
|
||||
# homeassistant.components.debugpy
|
||||
debugpy==1.6.7
|
||||
@ -604,7 +604,7 @@ env-canada==0.5.36
|
||||
ephem==4.1.2
|
||||
|
||||
# homeassistant.components.epson
|
||||
epson-projector==0.5.0
|
||||
epson-projector==0.5.1
|
||||
|
||||
# homeassistant.components.esphome
|
||||
esphome-dashboard-api==1.2.3
|
||||
@ -777,7 +777,7 @@ hole==0.8.0
|
||||
holidays==0.28
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20230906.1
|
||||
home-assistant-frontend==20230908.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.8.2
|
||||
@ -927,7 +927,7 @@ micloud==0.5
|
||||
mill-local==0.2.0
|
||||
|
||||
# homeassistant.components.mill
|
||||
millheater==0.11.1
|
||||
millheater==0.11.2
|
||||
|
||||
# homeassistant.components.minio
|
||||
minio==7.1.12
|
||||
@ -1235,7 +1235,7 @@ pyeconet==0.1.20
|
||||
pyefergy==22.1.1
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==1.9.1
|
||||
pyenphase==1.11.0
|
||||
|
||||
# homeassistant.components.everlights
|
||||
pyeverlights==0.1.0
|
||||
@ -1349,7 +1349,7 @@ pylitejet==0.5.0
|
||||
pylitterbot==2023.4.5
|
||||
|
||||
# homeassistant.components.lutron_caseta
|
||||
pylutron-caseta==0.18.1
|
||||
pylutron-caseta==0.18.2
|
||||
|
||||
# homeassistant.components.mailgun
|
||||
pymailgunner==1.4
|
||||
@ -1370,7 +1370,7 @@ pymeteoclimatic==0.0.6
|
||||
pymochad==0.2.0
|
||||
|
||||
# homeassistant.components.modbus
|
||||
pymodbus==3.4.1
|
||||
pymodbus==3.5.1
|
||||
|
||||
# homeassistant.components.monoprice
|
||||
pymonoprice==0.4
|
||||
@ -1423,7 +1423,7 @@ pyotgw==2.1.3
|
||||
pyotp==2.8.0
|
||||
|
||||
# homeassistant.components.overkiz
|
||||
pyoverkiz==1.10.1
|
||||
pyoverkiz==1.9.0
|
||||
|
||||
# homeassistant.components.openweathermap
|
||||
pyowm==3.2.0
|
||||
@ -1729,10 +1729,10 @@ screenlogicpy==0.8.2
|
||||
securetar==2023.3.0
|
||||
|
||||
# homeassistant.components.sense
|
||||
sense-energy==0.12.0
|
||||
sense-energy==0.12.1
|
||||
|
||||
# homeassistant.components.emulated_kasa
|
||||
sense_energy==0.12.0
|
||||
sense_energy==0.12.1
|
||||
|
||||
# homeassistant.components.sensirion_ble
|
||||
sensirion-ble==0.1.0
|
||||
@ -2036,7 +2036,7 @@ youtubeaio==1.1.5
|
||||
zamg==0.3.0
|
||||
|
||||
# homeassistant.components.zeroconf
|
||||
zeroconf==0.91.1
|
||||
zeroconf==0.98.0
|
||||
|
||||
# homeassistant.components.zeversolar
|
||||
zeversolar==0.3.1
|
||||
|
@ -2471,6 +2471,75 @@ async def test_thermostat(hass: HomeAssistant) -> None:
|
||||
assert call.data["preset_mode"] == "eco"
|
||||
|
||||
|
||||
async def test_no_current_target_temp_adjusting_temp(hass: HomeAssistant) -> None:
|
||||
"""Test thermostat adjusting temp with no initial target temperature."""
|
||||
hass.config.units = US_CUSTOMARY_SYSTEM
|
||||
device = (
|
||||
"climate.test_thermostat",
|
||||
"cool",
|
||||
{
|
||||
"temperature": None,
|
||||
"target_temp_high": None,
|
||||
"target_temp_low": None,
|
||||
"current_temperature": 75.0,
|
||||
"friendly_name": "Test Thermostat",
|
||||
"supported_features": 1 | 2 | 4 | 128,
|
||||
"hvac_modes": ["off", "heat", "cool", "auto", "dry", "fan_only"],
|
||||
"preset_mode": None,
|
||||
"preset_modes": ["eco"],
|
||||
"min_temp": 50,
|
||||
"max_temp": 90,
|
||||
},
|
||||
)
|
||||
appliance = await discovery_test(device, hass)
|
||||
|
||||
assert appliance["endpointId"] == "climate#test_thermostat"
|
||||
assert appliance["displayCategories"][0] == "THERMOSTAT"
|
||||
assert appliance["friendlyName"] == "Test Thermostat"
|
||||
|
||||
capabilities = assert_endpoint_capabilities(
|
||||
appliance,
|
||||
"Alexa.PowerController",
|
||||
"Alexa.ThermostatController",
|
||||
"Alexa.TemperatureSensor",
|
||||
"Alexa.EndpointHealth",
|
||||
"Alexa",
|
||||
)
|
||||
|
||||
properties = await reported_properties(hass, "climate#test_thermostat")
|
||||
properties.assert_equal("Alexa.ThermostatController", "thermostatMode", "COOL")
|
||||
properties.assert_not_has_property(
|
||||
"Alexa.ThermostatController",
|
||||
"targetSetpoint",
|
||||
)
|
||||
properties.assert_equal(
|
||||
"Alexa.TemperatureSensor", "temperature", {"value": 75.0, "scale": "FAHRENHEIT"}
|
||||
)
|
||||
|
||||
thermostat_capability = get_capability(capabilities, "Alexa.ThermostatController")
|
||||
assert thermostat_capability is not None
|
||||
configuration = thermostat_capability["configuration"]
|
||||
assert configuration["supportsScheduling"] is False
|
||||
|
||||
supported_modes = ["OFF", "HEAT", "COOL", "AUTO", "ECO", "CUSTOM"]
|
||||
for mode in supported_modes:
|
||||
assert mode in configuration["supportedModes"]
|
||||
|
||||
# Adjust temperature where target temp is not set
|
||||
msg = await assert_request_fails(
|
||||
"Alexa.ThermostatController",
|
||||
"AdjustTargetTemperature",
|
||||
"climate#test_thermostat",
|
||||
"climate.set_temperature",
|
||||
hass,
|
||||
payload={"targetSetpointDelta": {"value": -5.0, "scale": "KELVIN"}},
|
||||
)
|
||||
assert msg["event"]["payload"]["type"] == "INVALID_TARGET_STATE"
|
||||
assert msg["event"]["payload"]["message"] == (
|
||||
"The current target temperature is not set, cannot adjust target temperature"
|
||||
)
|
||||
|
||||
|
||||
async def test_thermostat_dual(hass: HomeAssistant) -> None:
|
||||
"""Test thermostat discovery with auto mode, with upper and lower target temperatures."""
|
||||
hass.config.units = US_CUSTOMARY_SYSTEM
|
||||
|
@ -96,6 +96,28 @@ async def test_api_state_change_of_non_existing_entity(
|
||||
assert hass.states.get("test_entity.that_does_not_exist").state == new_state
|
||||
|
||||
|
||||
async def test_api_state_change_with_bad_entity_id(
|
||||
hass: HomeAssistant, mock_api_client: TestClient
|
||||
) -> None:
|
||||
"""Test if API sends appropriate error if we omit state."""
|
||||
resp = await mock_api_client.post(
|
||||
"/api/states/bad.entity.id", json={"state": "new_state"}
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||
|
||||
|
||||
async def test_api_state_change_with_bad_state(
|
||||
hass: HomeAssistant, mock_api_client: TestClient
|
||||
) -> None:
|
||||
"""Test if API sends appropriate error if we omit state."""
|
||||
resp = await mock_api_client.post(
|
||||
"/api/states/test.test", json={"state": "x" * 256}
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||
|
||||
|
||||
async def test_api_state_change_with_bad_data(
|
||||
hass: HomeAssistant, mock_api_client: TestClient
|
||||
) -> None:
|
||||
|
27
tests/components/freebox/common.py
Normal file
27
tests/components/freebox/common.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""Common methods used across tests for Freebox."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.freebox.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import MOCK_HOST, MOCK_PORT
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_platform(hass: HomeAssistant, platform: str) -> MockConfigEntry:
|
||||
"""Set up the Freebox platform."""
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT},
|
||||
unique_id=MOCK_HOST,
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
with patch("homeassistant.components.freebox.PLATFORMS", [platform]):
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_entry
|
@ -1986,7 +1986,7 @@ DATA_HOME_GET_NODES = [
|
||||
"category": "kfb",
|
||||
"group": {"label": ""},
|
||||
"id": 9,
|
||||
"label": "Télécommande I",
|
||||
"label": "Télécommande",
|
||||
"name": "node_9",
|
||||
"props": {
|
||||
"Address": 5,
|
||||
@ -2067,7 +2067,7 @@ DATA_HOME_GET_NODES = [
|
||||
"category": "dws",
|
||||
"group": {"label": "Entrée"},
|
||||
"id": 11,
|
||||
"label": "dws i",
|
||||
"label": "Ouverture porte",
|
||||
"name": "node_11",
|
||||
"props": {
|
||||
"Address": 6,
|
||||
@ -2259,7 +2259,7 @@ DATA_HOME_GET_NODES = [
|
||||
"category": "pir",
|
||||
"group": {"label": "Salon"},
|
||||
"id": 26,
|
||||
"label": "Salon Détecteur s",
|
||||
"label": "Détecteur",
|
||||
"name": "node_26",
|
||||
"props": {
|
||||
"Address": 9,
|
||||
|
71
tests/components/freebox/test_sensor.py
Normal file
71
tests/components/freebox/test_sensor.py
Normal file
@ -0,0 +1,71 @@
|
||||
"""Tests for the Freebox sensors."""
|
||||
from copy import deepcopy
|
||||
from unittest.mock import Mock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
|
||||
from homeassistant.components.freebox import SCAN_INTERVAL
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .common import setup_platform
|
||||
from .const import DATA_HOME_GET_NODES, DATA_STORAGE_GET_DISKS
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
|
||||
async def test_disk(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, router: Mock
|
||||
) -> None:
|
||||
"""Test disk sensor."""
|
||||
await setup_platform(hass, SENSOR_DOMAIN)
|
||||
|
||||
# Initial state
|
||||
assert (
|
||||
router().storage.get_disks.return_value[2]["partitions"][0]["total_bytes"]
|
||||
== 1960000000000
|
||||
)
|
||||
|
||||
assert (
|
||||
router().storage.get_disks.return_value[2]["partitions"][0]["free_bytes"]
|
||||
== 1730000000000
|
||||
)
|
||||
|
||||
assert hass.states.get("sensor.freebox_free_space").state == "88.27"
|
||||
|
||||
# Simulate a changed storage size
|
||||
data_storage_get_disks_changed = deepcopy(DATA_STORAGE_GET_DISKS)
|
||||
data_storage_get_disks_changed[2]["partitions"][0]["free_bytes"] = 880000000000
|
||||
router().storage.get_disks.return_value = data_storage_get_disks_changed
|
||||
# Simulate an update
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
# To execute the save
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("sensor.freebox_free_space").state == "44.9"
|
||||
|
||||
|
||||
async def test_battery(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, router: Mock
|
||||
) -> None:
|
||||
"""Test battery sensor."""
|
||||
await setup_platform(hass, SENSOR_DOMAIN)
|
||||
|
||||
assert hass.states.get("sensor.telecommande_niveau_de_batterie").state == "100"
|
||||
assert hass.states.get("sensor.ouverture_porte_niveau_de_batterie").state == "100"
|
||||
assert hass.states.get("sensor.detecteur_niveau_de_batterie").state == "100"
|
||||
|
||||
# Simulate a changed battery
|
||||
data_home_get_nodes_changed = deepcopy(DATA_HOME_GET_NODES)
|
||||
data_home_get_nodes_changed[2]["show_endpoints"][3]["value"] = 25
|
||||
data_home_get_nodes_changed[3]["show_endpoints"][3]["value"] = 50
|
||||
data_home_get_nodes_changed[4]["show_endpoints"][3]["value"] = 75
|
||||
router().home.get_home_nodes.return_value = data_home_get_nodes_changed
|
||||
# Simulate an update
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
# To execute the save
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("sensor.telecommande_niveau_de_batterie").state == "25"
|
||||
assert hass.states.get("sensor.ouverture_porte_niveau_de_batterie").state == "50"
|
||||
assert hass.states.get("sensor.detecteur_niveau_de_batterie").state == "75"
|
@ -626,6 +626,16 @@ async def test_battery_sensor_state_via_mqtt(
|
||||
"unit_of_measurement": "%",
|
||||
}
|
||||
|
||||
# Test polled state update
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"tasmota_49A3BC/stat/STATUS11",
|
||||
'{"StatusSTS":{"BatteryPercentage":50}}',
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("sensor.tasmota_battery_level")
|
||||
assert state.state == "50"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status_sensor_disabled", [False])
|
||||
async def test_single_shot_status_sensor_state_via_mqtt(
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Test the thread websocket API."""
|
||||
|
||||
import dataclasses
|
||||
import time
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
@ -191,50 +190,49 @@ async def test_diagnostics(
|
||||
"""Test diagnostics for thread routers."""
|
||||
cache = mock_async_zeroconf.zeroconf.cache = DNSCache()
|
||||
|
||||
now = time.monotonic() * 1000
|
||||
cache.async_add_records(
|
||||
[
|
||||
*TEST_ZEROCONF_RECORD_1.dns_addresses(created=now),
|
||||
TEST_ZEROCONF_RECORD_1.dns_service(created=now),
|
||||
TEST_ZEROCONF_RECORD_1.dns_text(created=now),
|
||||
TEST_ZEROCONF_RECORD_1.dns_pointer(created=now),
|
||||
*TEST_ZEROCONF_RECORD_1.dns_addresses(),
|
||||
TEST_ZEROCONF_RECORD_1.dns_service(),
|
||||
TEST_ZEROCONF_RECORD_1.dns_text(),
|
||||
TEST_ZEROCONF_RECORD_1.dns_pointer(),
|
||||
]
|
||||
)
|
||||
cache.async_add_records(
|
||||
[
|
||||
*TEST_ZEROCONF_RECORD_2.dns_addresses(created=now),
|
||||
TEST_ZEROCONF_RECORD_2.dns_service(created=now),
|
||||
TEST_ZEROCONF_RECORD_2.dns_text(created=now),
|
||||
TEST_ZEROCONF_RECORD_2.dns_pointer(created=now),
|
||||
*TEST_ZEROCONF_RECORD_2.dns_addresses(),
|
||||
TEST_ZEROCONF_RECORD_2.dns_service(),
|
||||
TEST_ZEROCONF_RECORD_2.dns_text(),
|
||||
TEST_ZEROCONF_RECORD_2.dns_pointer(),
|
||||
]
|
||||
)
|
||||
# Test for invalid cache
|
||||
cache.async_add_records([TEST_ZEROCONF_RECORD_3.dns_pointer(created=now)])
|
||||
cache.async_add_records([TEST_ZEROCONF_RECORD_3.dns_pointer()])
|
||||
# Test for invalid record
|
||||
cache.async_add_records(
|
||||
[
|
||||
*TEST_ZEROCONF_RECORD_4.dns_addresses(created=now),
|
||||
TEST_ZEROCONF_RECORD_4.dns_service(created=now),
|
||||
TEST_ZEROCONF_RECORD_4.dns_text(created=now),
|
||||
TEST_ZEROCONF_RECORD_4.dns_pointer(created=now),
|
||||
*TEST_ZEROCONF_RECORD_4.dns_addresses(),
|
||||
TEST_ZEROCONF_RECORD_4.dns_service(),
|
||||
TEST_ZEROCONF_RECORD_4.dns_text(),
|
||||
TEST_ZEROCONF_RECORD_4.dns_pointer(),
|
||||
]
|
||||
)
|
||||
# Test for record without xa
|
||||
cache.async_add_records(
|
||||
[
|
||||
*TEST_ZEROCONF_RECORD_5.dns_addresses(created=now),
|
||||
TEST_ZEROCONF_RECORD_5.dns_service(created=now),
|
||||
TEST_ZEROCONF_RECORD_5.dns_text(created=now),
|
||||
TEST_ZEROCONF_RECORD_5.dns_pointer(created=now),
|
||||
*TEST_ZEROCONF_RECORD_5.dns_addresses(),
|
||||
TEST_ZEROCONF_RECORD_5.dns_service(),
|
||||
TEST_ZEROCONF_RECORD_5.dns_text(),
|
||||
TEST_ZEROCONF_RECORD_5.dns_pointer(),
|
||||
]
|
||||
)
|
||||
# Test for record without xp
|
||||
cache.async_add_records(
|
||||
[
|
||||
*TEST_ZEROCONF_RECORD_6.dns_addresses(created=now),
|
||||
TEST_ZEROCONF_RECORD_6.dns_service(created=now),
|
||||
TEST_ZEROCONF_RECORD_6.dns_text(created=now),
|
||||
TEST_ZEROCONF_RECORD_6.dns_pointer(created=now),
|
||||
*TEST_ZEROCONF_RECORD_6.dns_addresses(),
|
||||
TEST_ZEROCONF_RECORD_6.dns_service(),
|
||||
TEST_ZEROCONF_RECORD_6.dns_text(),
|
||||
TEST_ZEROCONF_RECORD_6.dns_pointer(),
|
||||
]
|
||||
)
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
@ -153,9 +153,66 @@ async def test_legacy_config_entry(hass: HomeAssistant) -> None:
|
||||
assert len(er.async_entries_for_config_entry(registry, entry.entry_id)) == 30
|
||||
|
||||
|
||||
async def test_v4_weather(hass: HomeAssistant) -> None:
|
||||
async def test_v4_weather(hass: HomeAssistant, tomorrowio_config_entry_update) -> None:
|
||||
"""Test v4 weather data."""
|
||||
weather_state = await _setup(hass, API_V4_ENTRY_DATA)
|
||||
|
||||
tomorrowio_config_entry_update.assert_called_with(
|
||||
[
|
||||
"temperature",
|
||||
"humidity",
|
||||
"pressureSeaLevel",
|
||||
"windSpeed",
|
||||
"windDirection",
|
||||
"weatherCode",
|
||||
"visibility",
|
||||
"pollutantO3",
|
||||
"windGust",
|
||||
"cloudCover",
|
||||
"precipitationType",
|
||||
"pollutantCO",
|
||||
"mepIndex",
|
||||
"mepHealthConcern",
|
||||
"mepPrimaryPollutant",
|
||||
"cloudBase",
|
||||
"cloudCeiling",
|
||||
"cloudCover",
|
||||
"dewPoint",
|
||||
"epaIndex",
|
||||
"epaHealthConcern",
|
||||
"epaPrimaryPollutant",
|
||||
"temperatureApparent",
|
||||
"fireIndex",
|
||||
"pollutantNO2",
|
||||
"pollutantO3",
|
||||
"particulateMatter10",
|
||||
"particulateMatter25",
|
||||
"grassIndex",
|
||||
"treeIndex",
|
||||
"weedIndex",
|
||||
"precipitationType",
|
||||
"pressureSurfaceLevel",
|
||||
"solarGHI",
|
||||
"pollutantSO2",
|
||||
"uvIndex",
|
||||
"uvHealthConcern",
|
||||
"windGust",
|
||||
],
|
||||
[
|
||||
"temperatureMin",
|
||||
"temperatureMax",
|
||||
"dewPoint",
|
||||
"humidity",
|
||||
"windSpeed",
|
||||
"windDirection",
|
||||
"weatherCode",
|
||||
"precipitationIntensityAvg",
|
||||
"precipitationProbability",
|
||||
],
|
||||
nowcast_timestep=60,
|
||||
location="80.0,80.0",
|
||||
)
|
||||
|
||||
assert weather_state.state == ATTR_CONDITION_SUNNY
|
||||
assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION
|
||||
assert len(weather_state.attributes[ATTR_FORECAST]) == 14
|
||||
|
@ -78,7 +78,7 @@ async def test_exception_connection(hass: HomeAssistant, side_effect, error) ->
|
||||
|
||||
# Should be recoverable after hits error
|
||||
with patch(
|
||||
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_all_devices",
|
||||
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_devices_data",
|
||||
return_value={
|
||||
"wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G",
|
||||
"ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;",
|
||||
@ -191,7 +191,7 @@ async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) ->
|
||||
|
||||
# Should be recoverable after hits error
|
||||
with patch(
|
||||
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_all_devices",
|
||||
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_devices_data",
|
||||
return_value={
|
||||
"wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G",
|
||||
"ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;",
|
||||
|
@ -1234,27 +1234,27 @@ EMPTY_LISTENERS = {"all": False, "entities": [], "domains": [], "time": False}
|
||||
|
||||
ERR_MSG = {"type": "result", "success": False}
|
||||
|
||||
VARIABLE_ERROR_UNDEFINED_FUNC = {
|
||||
EVENT_UNDEFINED_FUNC_1 = {
|
||||
"error": "'my_unknown_func' is undefined",
|
||||
"level": "ERROR",
|
||||
}
|
||||
TEMPLATE_ERROR_UNDEFINED_FUNC = {
|
||||
"code": "template_error",
|
||||
"message": "UndefinedError: 'my_unknown_func' is undefined",
|
||||
EVENT_UNDEFINED_FUNC_2 = {
|
||||
"error": "UndefinedError: 'my_unknown_func' is undefined",
|
||||
"level": "ERROR",
|
||||
}
|
||||
|
||||
VARIABLE_WARNING_UNDEFINED_VAR = {
|
||||
EVENT_UNDEFINED_VAR_WARN = {
|
||||
"error": "'my_unknown_var' is undefined",
|
||||
"level": "WARNING",
|
||||
}
|
||||
TEMPLATE_ERROR_UNDEFINED_VAR = {
|
||||
"code": "template_error",
|
||||
"message": "UndefinedError: 'my_unknown_var' is undefined",
|
||||
EVENT_UNDEFINED_VAR_ERR = {
|
||||
"error": "UndefinedError: 'my_unknown_var' is undefined",
|
||||
"level": "ERROR",
|
||||
}
|
||||
|
||||
TEMPLATE_ERROR_UNDEFINED_FILTER = {
|
||||
"code": "template_error",
|
||||
"message": "TemplateAssertionError: No filter named 'unknown_filter'.",
|
||||
EVENT_UNDEFINED_FILTER = {
|
||||
"error": "TemplateAssertionError: No filter named 'unknown_filter'.",
|
||||
"level": "ERROR",
|
||||
}
|
||||
|
||||
|
||||
@ -1264,16 +1264,19 @@ TEMPLATE_ERROR_UNDEFINED_FILTER = {
|
||||
(
|
||||
"{{ my_unknown_func() + 1 }}",
|
||||
[
|
||||
{"type": "event", "event": VARIABLE_ERROR_UNDEFINED_FUNC},
|
||||
ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FUNC},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ my_unknown_var }}",
|
||||
[
|
||||
{"type": "event", "event": VARIABLE_WARNING_UNDEFINED_VAR},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_WARN},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": VARIABLE_WARNING_UNDEFINED_VAR},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_WARN},
|
||||
{
|
||||
"type": "event",
|
||||
"event": {"result": "", "listeners": EMPTY_LISTENERS},
|
||||
@ -1282,11 +1285,19 @@ TEMPLATE_ERROR_UNDEFINED_FILTER = {
|
||||
),
|
||||
(
|
||||
"{{ my_unknown_var + 1 }}",
|
||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_VAR}],
|
||||
[
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ now() | unknown_filter }}",
|
||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FILTER}],
|
||||
[
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -1325,16 +1336,20 @@ async def test_render_template_with_error(
|
||||
(
|
||||
"{{ my_unknown_func() + 1 }}",
|
||||
[
|
||||
{"type": "event", "event": VARIABLE_ERROR_UNDEFINED_FUNC},
|
||||
ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FUNC},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ my_unknown_var }}",
|
||||
[
|
||||
{"type": "event", "event": VARIABLE_WARNING_UNDEFINED_VAR},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_WARN},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": VARIABLE_WARNING_UNDEFINED_VAR},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_WARN},
|
||||
{
|
||||
"type": "event",
|
||||
"event": {"result": "", "listeners": EMPTY_LISTENERS},
|
||||
@ -1343,11 +1358,19 @@ async def test_render_template_with_error(
|
||||
),
|
||||
(
|
||||
"{{ my_unknown_var + 1 }}",
|
||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_VAR}],
|
||||
[
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ now() | unknown_filter }}",
|
||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FILTER}],
|
||||
[
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -1386,19 +1409,35 @@ async def test_render_template_with_timeout_and_error(
|
||||
[
|
||||
(
|
||||
"{{ my_unknown_func() + 1 }}",
|
||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FUNC}],
|
||||
[
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ my_unknown_var }}",
|
||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_VAR}],
|
||||
[
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ my_unknown_var + 1 }}",
|
||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_VAR}],
|
||||
[
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ now() | unknown_filter }}",
|
||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FILTER}],
|
||||
[
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -1409,7 +1448,73 @@ async def test_render_template_strict_with_timeout_and_error(
|
||||
template: str,
|
||||
expected_events: list[dict[str, str]],
|
||||
) -> None:
|
||||
"""Test a template with an error with a timeout."""
|
||||
"""Test a template with an error with a timeout.
|
||||
|
||||
In this test report_errors is enabled.
|
||||
"""
|
||||
caplog.set_level(logging.INFO)
|
||||
await websocket_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "render_template",
|
||||
"template": template,
|
||||
"timeout": 5,
|
||||
"strict": True,
|
||||
"report_errors": True,
|
||||
}
|
||||
)
|
||||
|
||||
for expected_event in expected_events:
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
for key, value in expected_event.items():
|
||||
assert msg[key] == value
|
||||
|
||||
assert "Template variable error" not in caplog.text
|
||||
assert "Template variable warning" not in caplog.text
|
||||
assert "TemplateError" not in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("template", "expected_events"),
|
||||
[
|
||||
(
|
||||
"{{ my_unknown_func() + 1 }}",
|
||||
[
|
||||
{"type": "result", "success": True, "result": None},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ my_unknown_var }}",
|
||||
[
|
||||
{"type": "result", "success": True, "result": None},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ my_unknown_var + 1 }}",
|
||||
[
|
||||
{"type": "result", "success": True, "result": None},
|
||||
],
|
||||
),
|
||||
(
|
||||
"{{ now() | unknown_filter }}",
|
||||
[
|
||||
{"type": "result", "success": True, "result": None},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_render_template_strict_with_timeout_and_error_2(
|
||||
hass: HomeAssistant,
|
||||
websocket_client,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
template: str,
|
||||
expected_events: list[dict[str, str]],
|
||||
) -> None:
|
||||
"""Test a template with an error with a timeout.
|
||||
|
||||
In this test report_errors is disabled.
|
||||
"""
|
||||
caplog.set_level(logging.INFO)
|
||||
await websocket_client.send_json(
|
||||
{
|
||||
@ -1427,30 +1532,164 @@ async def test_render_template_strict_with_timeout_and_error(
|
||||
for key, value in expected_event.items():
|
||||
assert msg[key] == value
|
||||
|
||||
assert "Template variable error" not in caplog.text
|
||||
assert "Template variable warning" not in caplog.text
|
||||
assert "TemplateError" not in caplog.text
|
||||
assert "TemplateError" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("template", "expected_events_1", "expected_events_2"),
|
||||
[
|
||||
(
|
||||
"{{ now() | random }}",
|
||||
[
|
||||
{
|
||||
"type": "event",
|
||||
"event": {
|
||||
"error": "TypeError: object of type 'datetime.datetime' has no len()",
|
||||
"level": "ERROR",
|
||||
},
|
||||
},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{
|
||||
"type": "event",
|
||||
"event": {
|
||||
"error": "TypeError: object of type 'datetime.datetime' has no len()",
|
||||
"level": "ERROR",
|
||||
},
|
||||
},
|
||||
],
|
||||
[],
|
||||
),
|
||||
(
|
||||
"{{ float(states.sensor.foo.state) + 1 }}",
|
||||
[
|
||||
{
|
||||
"type": "event",
|
||||
"event": {
|
||||
"error": "UndefinedError: 'None' has no attribute 'state'",
|
||||
"level": "ERROR",
|
||||
},
|
||||
},
|
||||
{"type": "result", "success": True, "result": None},
|
||||
{
|
||||
"type": "event",
|
||||
"event": {
|
||||
"error": "UndefinedError: 'None' has no attribute 'state'",
|
||||
"level": "ERROR",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "event",
|
||||
"event": {
|
||||
"result": 3.0,
|
||||
"listeners": EMPTY_LISTENERS | {"entities": ["sensor.foo"]},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_render_template_error_in_template_code(
|
||||
hass: HomeAssistant, websocket_client, caplog: pytest.LogCaptureFixture
|
||||
hass: HomeAssistant,
|
||||
websocket_client,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
template: str,
|
||||
expected_events_1: list[dict[str, str]],
|
||||
expected_events_2: list[dict[str, str]],
|
||||
) -> None:
|
||||
"""Test a template that will throw in template.py."""
|
||||
"""Test a template that will throw in template.py.
|
||||
|
||||
In this test report_errors is enabled.
|
||||
"""
|
||||
await websocket_client.send_json(
|
||||
{"id": 5, "type": "render_template", "template": "{{ now() | random }}"}
|
||||
{
|
||||
"id": 5,
|
||||
"type": "render_template",
|
||||
"template": template,
|
||||
"report_errors": True,
|
||||
}
|
||||
)
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
assert msg["type"] == const.TYPE_RESULT
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
|
||||
for expected_event in expected_events_1:
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
for key, value in expected_event.items():
|
||||
assert msg[key] == value
|
||||
|
||||
hass.states.async_set("sensor.foo", "2")
|
||||
|
||||
for expected_event in expected_events_2:
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
for key, value in expected_event.items():
|
||||
assert msg[key] == value
|
||||
|
||||
assert "Template variable error" not in caplog.text
|
||||
assert "Template variable warning" not in caplog.text
|
||||
assert "TemplateError" not in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("template", "expected_events_1", "expected_events_2"),
|
||||
[
|
||||
(
|
||||
"{{ now() | random }}",
|
||||
[
|
||||
{"type": "result", "success": True, "result": None},
|
||||
],
|
||||
[],
|
||||
),
|
||||
(
|
||||
"{{ float(states.sensor.foo.state) + 1 }}",
|
||||
[
|
||||
{"type": "result", "success": True, "result": None},
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "event",
|
||||
"event": {
|
||||
"result": 3.0,
|
||||
"listeners": EMPTY_LISTENERS | {"entities": ["sensor.foo"]},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_render_template_error_in_template_code_2(
|
||||
hass: HomeAssistant,
|
||||
websocket_client,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
template: str,
|
||||
expected_events_1: list[dict[str, str]],
|
||||
expected_events_2: list[dict[str, str]],
|
||||
) -> None:
|
||||
"""Test a template that will throw in template.py.
|
||||
|
||||
In this test report_errors is disabled.
|
||||
"""
|
||||
await websocket_client.send_json(
|
||||
{"id": 5, "type": "render_template", "template": template}
|
||||
)
|
||||
|
||||
for expected_event in expected_events_1:
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
for key, value in expected_event.items():
|
||||
assert msg[key] == value
|
||||
|
||||
hass.states.async_set("sensor.foo", "2")
|
||||
|
||||
for expected_event in expected_events_2:
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
for key, value in expected_event.items():
|
||||
assert msg[key] == value
|
||||
|
||||
assert "TemplateError" in caplog.text
|
||||
|
||||
|
||||
async def test_render_template_with_delayed_error(
|
||||
hass: HomeAssistant, websocket_client, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
@ -1512,7 +1751,10 @@ async def test_render_template_with_delayed_error(
|
||||
assert msg["id"] == 5
|
||||
assert msg["type"] == "event"
|
||||
event = msg["event"]
|
||||
assert event == {"error": "UndefinedError: 'explode' is undefined"}
|
||||
assert event == {
|
||||
"error": "UndefinedError: 'explode' is undefined",
|
||||
"level": "ERROR",
|
||||
}
|
||||
|
||||
assert "Template variable error" not in caplog.text
|
||||
assert "Template variable warning" not in caplog.text
|
||||
|
@ -3239,27 +3239,6 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(
|
||||
]
|
||||
|
||||
|
||||
async def test_async_track_template_result_raise_on_template_error(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test that we raise as soon as we encounter a failed template."""
|
||||
|
||||
with pytest.raises(TemplateError):
|
||||
async_track_template_result(
|
||||
hass,
|
||||
[
|
||||
TrackTemplate(
|
||||
Template(
|
||||
"{{ states.switch | function_that_does_not_exist | list }}"
|
||||
),
|
||||
None,
|
||||
),
|
||||
],
|
||||
ha.callback(lambda event, updates: None),
|
||||
raise_on_template_error=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_track_template_with_time(hass: HomeAssistant) -> None:
|
||||
"""Test tracking template with time."""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user