mirror of
https://github.com/home-assistant/core.git
synced 2025-08-05 13:45:12 +02:00
Merge remote-tracking branch 'upstream/onvif_renew' into onvif_renew
This commit is contained in:
@@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.core import CALLBACK_TYPE, callback
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.helpers.storage import Store
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -19,7 +19,7 @@ class AbstractConfig(ABC):
|
||||
|
||||
_unsub_proactive_report: asyncio.Task[CALLBACK_TYPE] | None = None
|
||||
|
||||
def __init__(self, hass):
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize abstract config."""
|
||||
self.hass = hass
|
||||
self._store = None
|
||||
|
@@ -199,14 +199,10 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
||||
# Don't migrate if there's a YAML config
|
||||
return
|
||||
|
||||
for state in self.hass.states.async_all():
|
||||
async_expose_entity(
|
||||
self.hass,
|
||||
CLOUD_ALEXA,
|
||||
state.entity_id,
|
||||
self._should_expose_legacy(state.entity_id),
|
||||
)
|
||||
for entity_id in self._prefs.alexa_entity_configs:
|
||||
for entity_id in {
|
||||
*self.hass.states.async_entity_ids(),
|
||||
*self._prefs.alexa_entity_configs,
|
||||
}:
|
||||
async_expose_entity(
|
||||
self.hass,
|
||||
CLOUD_ALEXA,
|
||||
@@ -220,8 +216,18 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
||||
|
||||
async def on_hass_started(hass):
|
||||
if self._prefs.alexa_settings_version != ALEXA_SETTINGS_VERSION:
|
||||
if self._prefs.alexa_settings_version < 2:
|
||||
if self._prefs.alexa_settings_version < 2 or (
|
||||
# Recover from a bug we had in 2023.5.0 where entities didn't get exposed
|
||||
self._prefs.alexa_settings_version < 3
|
||||
and not any(
|
||||
settings.get("should_expose", False)
|
||||
for settings in async_get_assistant_settings(
|
||||
hass, CLOUD_ALEXA
|
||||
).values()
|
||||
)
|
||||
):
|
||||
self._migrate_alexa_entity_settings_v1()
|
||||
|
||||
await self._prefs.async_update(
|
||||
alexa_settings_version=ALEXA_SETTINGS_VERSION
|
||||
)
|
||||
|
@@ -12,6 +12,7 @@ from homeassistant.components.google_assistant import DOMAIN as GOOGLE_DOMAIN
|
||||
from homeassistant.components.google_assistant.helpers import AbstractConfig
|
||||
from homeassistant.components.homeassistant.exposed_entities import (
|
||||
async_expose_entity,
|
||||
async_get_assistant_settings,
|
||||
async_get_entity_settings,
|
||||
async_listen_entity_updates,
|
||||
async_set_assistant_option,
|
||||
@@ -175,23 +176,10 @@ class CloudGoogleConfig(AbstractConfig):
|
||||
# Don't migrate if there's a YAML config
|
||||
return
|
||||
|
||||
for state in self.hass.states.async_all():
|
||||
entity_id = state.entity_id
|
||||
async_expose_entity(
|
||||
self.hass,
|
||||
CLOUD_GOOGLE,
|
||||
entity_id,
|
||||
self._should_expose_legacy(entity_id),
|
||||
)
|
||||
if _2fa_disabled := (self._2fa_disabled_legacy(entity_id) is not None):
|
||||
async_set_assistant_option(
|
||||
self.hass,
|
||||
CLOUD_GOOGLE,
|
||||
entity_id,
|
||||
PREF_DISABLE_2FA,
|
||||
_2fa_disabled,
|
||||
)
|
||||
for entity_id in self._prefs.google_entity_configs:
|
||||
for entity_id in {
|
||||
*self.hass.states.async_entity_ids(),
|
||||
*self._prefs.google_entity_configs,
|
||||
}:
|
||||
async_expose_entity(
|
||||
self.hass,
|
||||
CLOUD_GOOGLE,
|
||||
@@ -213,8 +201,18 @@ class CloudGoogleConfig(AbstractConfig):
|
||||
|
||||
async def on_hass_started(hass: HomeAssistant) -> None:
|
||||
if self._prefs.google_settings_version != GOOGLE_SETTINGS_VERSION:
|
||||
if self._prefs.google_settings_version < 2:
|
||||
if self._prefs.google_settings_version < 2 or (
|
||||
# Recover from a bug we had in 2023.5.0 where entities didn't get exposed
|
||||
self._prefs.google_settings_version < 3
|
||||
and not any(
|
||||
settings.get("should_expose", False)
|
||||
for settings in async_get_assistant_settings(
|
||||
hass, CLOUD_GOOGLE
|
||||
).values()
|
||||
)
|
||||
):
|
||||
self._migrate_google_entity_settings_v1()
|
||||
|
||||
await self._prefs.async_update(
|
||||
google_settings_version=GOOGLE_SETTINGS_VERSION
|
||||
)
|
||||
|
@@ -41,8 +41,8 @@ STORAGE_KEY = DOMAIN
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_VERSION_MINOR = 2
|
||||
|
||||
ALEXA_SETTINGS_VERSION = 2
|
||||
GOOGLE_SETTINGS_VERSION = 2
|
||||
ALEXA_SETTINGS_VERSION = 3
|
||||
GOOGLE_SETTINGS_VERSION = 3
|
||||
|
||||
|
||||
class CloudPreferencesStore(Store):
|
||||
|
@@ -15,5 +15,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/elkm1",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["elkm1_lib"],
|
||||
"requirements": ["elkm1-lib==2.2.1"]
|
||||
"requirements": ["elkm1-lib==2.2.2"]
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ from aioesphomeapi import (
|
||||
NumberInfo,
|
||||
SelectInfo,
|
||||
SensorInfo,
|
||||
SensorState,
|
||||
SwitchInfo,
|
||||
TextSensorInfo,
|
||||
UserService,
|
||||
@@ -240,9 +241,18 @@ class RuntimeEntryData:
|
||||
current_state_by_type = self.state[state_type]
|
||||
current_state = current_state_by_type.get(key, _SENTINEL)
|
||||
subscription_key = (state_type, key)
|
||||
if current_state == state and subscription_key not in stale_state:
|
||||
if (
|
||||
current_state == state
|
||||
and subscription_key not in stale_state
|
||||
and not (
|
||||
type(state) is SensorState # pylint: disable=unidiomatic-typecheck
|
||||
and (platform_info := self.info.get(Platform.SENSOR))
|
||||
and (entity_info := platform_info.get(state.key))
|
||||
and (cast(SensorInfo, entity_info)).force_update
|
||||
)
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"%s: ignoring duplicate update with and key %s: %s",
|
||||
"%s: ignoring duplicate update with key %s: %s",
|
||||
self.name,
|
||||
key,
|
||||
state,
|
||||
|
@@ -15,7 +15,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioesphomeapi", "noiseprotocol"],
|
||||
"requirements": [
|
||||
"aioesphomeapi==13.7.2",
|
||||
"aioesphomeapi==13.7.3",
|
||||
"bluetooth-data-tools==0.4.0",
|
||||
"esphome-dashboard-api==1.2.3"
|
||||
],
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"""Integrate with FreeDNS Dynamic DNS service at freedns.afraid.org."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
@@ -53,11 +53,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
if result is False:
|
||||
return False
|
||||
|
||||
async def update_domain_callback(now):
|
||||
async def update_domain_callback(now: datetime) -> None:
|
||||
"""Update the FreeDNS entry."""
|
||||
await _update_freedns(hass, session, url, auth_token)
|
||||
|
||||
async_track_time_interval(hass, update_domain_callback, update_interval)
|
||||
async_track_time_interval(
|
||||
hass, update_domain_callback, update_interval, cancel_on_shutdown=True
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
@@ -590,7 +590,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
|
||||
await async_setup_addon_panel(hass, hassio)
|
||||
|
||||
# Setup hardware integration for the detected board type
|
||||
async def _async_setup_hardware_integration(hass):
|
||||
async def _async_setup_hardware_integration(_: datetime) -> None:
|
||||
"""Set up hardaware integration for the detected board type."""
|
||||
if (os_info := get_os_info(hass)) is None:
|
||||
# os info not yet fetched from supervisor, retry later
|
||||
@@ -610,7 +610,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
|
||||
)
|
||||
)
|
||||
|
||||
await _async_setup_hardware_integration(hass)
|
||||
await _async_setup_hardware_integration(datetime.now())
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"})
|
||||
|
@@ -31,7 +31,7 @@ from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.core import HassJob, HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.helpers import discovery, event
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -208,16 +208,18 @@ def setup(hass: HomeAssistant, base_config: ConfigType) -> bool: # noqa: C901
|
||||
|
||||
def _adapter_watchdog(now=None):
|
||||
_LOGGER.debug("Reached _adapter_watchdog")
|
||||
event.call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog)
|
||||
event.call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog_job)
|
||||
if not adapter.initialized:
|
||||
_LOGGER.info("Adapter not initialized; Trying to restart")
|
||||
hass.bus.fire(EVENT_HDMI_CEC_UNAVAILABLE)
|
||||
adapter.init()
|
||||
|
||||
_adapter_watchdog_job = HassJob(_adapter_watchdog, cancel_on_shutdown=True)
|
||||
|
||||
@callback
|
||||
def _async_initialized_callback(*_: Any):
|
||||
"""Add watchdog on initialization."""
|
||||
return event.async_call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog)
|
||||
return event.async_call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog_job)
|
||||
|
||||
hdmi_network.set_initialized_callback(_async_initialized_callback)
|
||||
|
||||
|
@@ -67,7 +67,7 @@ async def async_setup_platform(
|
||||
if isinstance(rest.last_exception, ssl.SSLError):
|
||||
_LOGGER.error(
|
||||
"Error connecting %s failed with %s",
|
||||
conf[CONF_RESOURCE],
|
||||
rest.url,
|
||||
rest.last_exception,
|
||||
)
|
||||
return
|
||||
|
@@ -50,6 +50,11 @@ class RestData:
|
||||
self.last_exception: Exception | None = None
|
||||
self.headers: httpx.Headers | None = None
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
"""Get url."""
|
||||
return self._resource
|
||||
|
||||
def set_url(self, url: str) -> None:
|
||||
"""Set url."""
|
||||
self._resource = url
|
||||
|
@@ -71,7 +71,7 @@ async def async_setup_platform(
|
||||
if isinstance(rest.last_exception, ssl.SSLError):
|
||||
_LOGGER.error(
|
||||
"Error connecting %s failed with %s",
|
||||
conf[CONF_RESOURCE],
|
||||
rest.url,
|
||||
rest.last_exception,
|
||||
)
|
||||
return
|
||||
|
@@ -6,8 +6,8 @@ from http import HTTPStatus
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
import httpx
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
@@ -30,8 +30,8 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers import config_validation as cv, template
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.template_entity import (
|
||||
TEMPLATE_ENTITY_BASE_SCHEMA,
|
||||
TemplateEntity,
|
||||
@@ -89,8 +89,8 @@ async def async_setup_platform(
|
||||
switch = RestSwitch(hass, config, unique_id)
|
||||
|
||||
req = await switch.get_device_state(hass)
|
||||
if req.status >= HTTPStatus.BAD_REQUEST:
|
||||
_LOGGER.error("Got non-ok response from resource: %s", req.status)
|
||||
if req.status_code >= HTTPStatus.BAD_REQUEST:
|
||||
_LOGGER.error("Got non-ok response from resource: %s", req.status_code)
|
||||
else:
|
||||
async_add_entities([switch])
|
||||
except (TypeError, ValueError):
|
||||
@@ -98,7 +98,7 @@ async def async_setup_platform(
|
||||
"Missing resource or schema in configuration. "
|
||||
"Add http:// or https:// to your URL"
|
||||
)
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError) as exc:
|
||||
except (asyncio.TimeoutError, httpx.RequestError) as exc:
|
||||
raise PlatformNotReady(f"No route to resource/endpoint: {resource}") from exc
|
||||
|
||||
|
||||
@@ -120,11 +120,11 @@ class RestSwitch(TemplateEntity, SwitchEntity):
|
||||
unique_id=unique_id,
|
||||
)
|
||||
|
||||
auth: aiohttp.BasicAuth | None = None
|
||||
auth: httpx.BasicAuth | None = None
|
||||
username: str | None = None
|
||||
if username := config.get(CONF_USERNAME):
|
||||
password: str = config[CONF_PASSWORD]
|
||||
auth = aiohttp.BasicAuth(username, password=password)
|
||||
auth = httpx.BasicAuth(username, password=password)
|
||||
|
||||
self._resource: str = config[CONF_RESOURCE]
|
||||
self._state_resource: str = config.get(CONF_STATE_RESOURCE) or self._resource
|
||||
@@ -155,13 +155,13 @@ class RestSwitch(TemplateEntity, SwitchEntity):
|
||||
try:
|
||||
req = await self.set_device_state(body_on_t)
|
||||
|
||||
if req.status == HTTPStatus.OK:
|
||||
if req.status_code == HTTPStatus.OK:
|
||||
self._attr_is_on = True
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Can't turn on %s. Is resource/endpoint offline?", self._resource
|
||||
)
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||
except (asyncio.TimeoutError, httpx.RequestError):
|
||||
_LOGGER.error("Error while switching on %s", self._resource)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
@@ -170,24 +170,24 @@ class RestSwitch(TemplateEntity, SwitchEntity):
|
||||
|
||||
try:
|
||||
req = await self.set_device_state(body_off_t)
|
||||
if req.status == HTTPStatus.OK:
|
||||
if req.status_code == HTTPStatus.OK:
|
||||
self._attr_is_on = False
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Can't turn off %s. Is resource/endpoint offline?", self._resource
|
||||
)
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||
except (asyncio.TimeoutError, httpx.RequestError):
|
||||
_LOGGER.error("Error while switching off %s", self._resource)
|
||||
|
||||
async def set_device_state(self, body: Any) -> aiohttp.ClientResponse:
|
||||
async def set_device_state(self, body: Any) -> httpx.Response:
|
||||
"""Send a state update to the device."""
|
||||
websession = async_get_clientsession(self.hass, self._verify_ssl)
|
||||
websession = get_async_client(self.hass, self._verify_ssl)
|
||||
|
||||
rendered_headers = template.render_complex(self._headers, parse_result=False)
|
||||
rendered_params = template.render_complex(self._params)
|
||||
|
||||
async with async_timeout.timeout(self._timeout):
|
||||
req: aiohttp.ClientResponse = await getattr(websession, self._method)(
|
||||
req: httpx.Response = await getattr(websession, self._method)(
|
||||
self._resource,
|
||||
auth=self._auth,
|
||||
data=bytes(body, "utf-8"),
|
||||
@@ -202,12 +202,12 @@ class RestSwitch(TemplateEntity, SwitchEntity):
|
||||
await self.get_device_state(self.hass)
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.exception("Timed out while fetching data")
|
||||
except aiohttp.ClientError as err:
|
||||
except httpx.RequestError as err:
|
||||
_LOGGER.exception("Error while fetching data: %s", err)
|
||||
|
||||
async def get_device_state(self, hass: HomeAssistant) -> aiohttp.ClientResponse:
|
||||
async def get_device_state(self, hass: HomeAssistant) -> httpx.Response:
|
||||
"""Get the latest data from REST API and update the state."""
|
||||
websession = async_get_clientsession(hass, self._verify_ssl)
|
||||
websession = get_async_client(hass, self._verify_ssl)
|
||||
|
||||
rendered_headers = template.render_complex(self._headers, parse_result=False)
|
||||
rendered_params = template.render_complex(self._params)
|
||||
@@ -219,7 +219,7 @@ class RestSwitch(TemplateEntity, SwitchEntity):
|
||||
headers=rendered_headers,
|
||||
params=rendered_params,
|
||||
)
|
||||
text = await req.text()
|
||||
text = req.text
|
||||
|
||||
if self._is_on_template is not None:
|
||||
text = self._is_on_template.async_render_with_possible_json_value(
|
||||
|
@@ -123,7 +123,7 @@ class SIAAlarmControlPanel(SIABaseEntity, AlarmControlPanelEntity):
|
||||
"""
|
||||
new_state = None
|
||||
if sia_event.code:
|
||||
new_state = self.entity_description.code_consequences[sia_event.code]
|
||||
new_state = self.entity_description.code_consequences.get(sia_event.code)
|
||||
if new_state is None:
|
||||
return False
|
||||
_LOGGER.debug("New state will be %s", new_state)
|
||||
|
@@ -132,7 +132,7 @@ class SIABinarySensor(SIABaseEntity, BinarySensorEntity):
|
||||
"""
|
||||
new_state = None
|
||||
if sia_event.code:
|
||||
new_state = self.entity_description.code_consequences[sia_event.code]
|
||||
new_state = self.entity_description.code_consequences.get(sia_event.code)
|
||||
if new_state is None:
|
||||
return False
|
||||
_LOGGER.debug("New state will be %s", new_state)
|
||||
|
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["hatasmota"],
|
||||
"mqtt": ["tasmota/discovery/#"],
|
||||
"requirements": ["hatasmota==0.6.4"]
|
||||
"requirements": ["hatasmota==0.6.5"]
|
||||
}
|
||||
|
@@ -12,6 +12,12 @@ from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_WEEKDAY, WEEKDAYS
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
TextSelector,
|
||||
)
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import CONF_FROM, CONF_TIME, CONF_TO, DOMAIN
|
||||
@@ -23,12 +29,17 @@ ERROR_MULTIPLE_STATION = "Found multiple stations with the specified name"
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
vol.Required(CONF_FROM): cv.string,
|
||||
vol.Required(CONF_TO): cv.string,
|
||||
vol.Optional(CONF_TIME): cv.string,
|
||||
vol.Required(CONF_WEEKDAY, default=WEEKDAYS): cv.multi_select(
|
||||
{day: day for day in WEEKDAYS}
|
||||
vol.Required(CONF_API_KEY): TextSelector(),
|
||||
vol.Required(CONF_FROM): TextSelector(),
|
||||
vol.Required(CONF_TO): TextSelector(),
|
||||
vol.Optional(CONF_TIME): TextSelector(),
|
||||
vol.Required(CONF_WEEKDAY, default=WEEKDAYS): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=WEEKDAYS,
|
||||
multiple=True,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
translation_key=CONF_WEEKDAY,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -28,5 +28,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"weekday": {
|
||||
"options": {
|
||||
"mon": "Monday",
|
||||
"tue": "Tuesday",
|
||||
"wed": "Wednesday",
|
||||
"thu": "Thursday",
|
||||
"fri": "Friday",
|
||||
"sat": "Saturday",
|
||||
"sun": "Sunday"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,11 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
import transmission_rpc
|
||||
from transmission_rpc.error import TransmissionError
|
||||
from transmission_rpc.error import (
|
||||
TransmissionAuthError,
|
||||
TransmissionConnectError,
|
||||
TransmissionError,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
@@ -137,14 +141,13 @@ async def get_api(hass, entry):
|
||||
_LOGGER.debug("Successfully connected to %s", host)
|
||||
return api
|
||||
|
||||
except TransmissionError as error:
|
||||
if "401: Unauthorized" in str(error):
|
||||
except TransmissionAuthError as error:
|
||||
_LOGGER.error("Credentials for Transmission client are not valid")
|
||||
raise AuthenticationError from error
|
||||
if "111: Connection refused" in str(error):
|
||||
except TransmissionConnectError as error:
|
||||
_LOGGER.error("Connecting to the Transmission client %s failed", host)
|
||||
raise CannotConnect from error
|
||||
|
||||
except TransmissionError as error:
|
||||
_LOGGER.error(error)
|
||||
raise UnknownError from error
|
||||
|
||||
|
@@ -137,7 +137,19 @@ class Endpoint:
|
||||
):
|
||||
cluster_handler_class = MultistateInput
|
||||
# end of ugly hack
|
||||
|
||||
try:
|
||||
cluster_handler = cluster_handler_class(cluster, self)
|
||||
except KeyError as err:
|
||||
_LOGGER.warning(
|
||||
"Cluster handler %s for cluster %s on endpoint %s is invalid: %s",
|
||||
cluster_handler_class,
|
||||
cluster,
|
||||
self,
|
||||
err,
|
||||
)
|
||||
continue
|
||||
|
||||
if cluster_handler.name == const.CLUSTER_HANDLER_POWER_CONFIGURATION:
|
||||
self._device.power_configuration_ch = cluster_handler
|
||||
elif cluster_handler.name == const.CLUSTER_HANDLER_IDENTIFY:
|
||||
|
@@ -156,7 +156,7 @@ aioecowitt==2023.01.0
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==13.7.2
|
||||
aioesphomeapi==13.7.3
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
@@ -644,7 +644,7 @@ elgato==4.0.1
|
||||
eliqonline==1.2.2
|
||||
|
||||
# homeassistant.components.elkm1
|
||||
elkm1-lib==2.2.1
|
||||
elkm1-lib==2.2.2
|
||||
|
||||
# homeassistant.components.elmax
|
||||
elmax_api==0.0.4
|
||||
@@ -881,7 +881,7 @@ hass_splunk==0.1.1
|
||||
hassil==1.0.6
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
hatasmota==0.6.4
|
||||
hatasmota==0.6.5
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.10.4
|
||||
|
@@ -146,7 +146,7 @@ aioecowitt==2023.01.0
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==13.7.2
|
||||
aioesphomeapi==13.7.3
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
@@ -506,7 +506,7 @@ easyenergy==0.3.0
|
||||
elgato==4.0.1
|
||||
|
||||
# homeassistant.components.elkm1
|
||||
elkm1-lib==2.2.1
|
||||
elkm1-lib==2.2.2
|
||||
|
||||
# homeassistant.components.elmax
|
||||
elmax_api==0.0.4
|
||||
@@ -679,7 +679,7 @@ hass-nabucasa==0.66.2
|
||||
hassil==1.0.6
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
hatasmota==0.6.4
|
||||
hatasmota==0.6.5
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.10.4
|
||||
|
@@ -542,11 +542,13 @@ async def test_alexa_handle_logout(
|
||||
assert len(mock_enable.return_value.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("alexa_settings_version", [1, 2])
|
||||
async def test_alexa_config_migrate_expose_entity_prefs(
|
||||
hass: HomeAssistant,
|
||||
cloud_prefs: CloudPreferences,
|
||||
cloud_stub,
|
||||
entity_registry: er.EntityRegistry,
|
||||
alexa_settings_version: int,
|
||||
) -> None:
|
||||
"""Test migrating Alexa entity config."""
|
||||
hass.state = CoreState.starting
|
||||
@@ -593,7 +595,7 @@ async def test_alexa_config_migrate_expose_entity_prefs(
|
||||
await cloud_prefs.async_update(
|
||||
alexa_enabled=True,
|
||||
alexa_report_state=False,
|
||||
alexa_settings_version=1,
|
||||
alexa_settings_version=alexa_settings_version,
|
||||
)
|
||||
expose_entity(hass, entity_migrated.entity_id, False)
|
||||
|
||||
@@ -641,6 +643,100 @@ async def test_alexa_config_migrate_expose_entity_prefs(
|
||||
}
|
||||
|
||||
|
||||
async def test_alexa_config_migrate_expose_entity_prefs_v2_no_exposed(
|
||||
hass: HomeAssistant,
|
||||
cloud_prefs: CloudPreferences,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test migrating Alexa entity config from v2 to v3 when no entity is exposed."""
|
||||
hass.state = CoreState.starting
|
||||
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set("light.state_only", "on")
|
||||
entity_migrated = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"test",
|
||||
"light_migrated",
|
||||
suggested_object_id="migrated",
|
||||
)
|
||||
await cloud_prefs.async_update(
|
||||
alexa_enabled=True,
|
||||
alexa_report_state=False,
|
||||
alexa_settings_version=2,
|
||||
)
|
||||
expose_entity(hass, "light.state_only", False)
|
||||
expose_entity(hass, entity_migrated.entity_id, False)
|
||||
|
||||
cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS]["light.state_only"] = {
|
||||
PREF_SHOULD_EXPOSE: True
|
||||
}
|
||||
cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS][entity_migrated.entity_id] = {
|
||||
PREF_SHOULD_EXPOSE: True
|
||||
}
|
||||
conf = alexa_config.CloudAlexaConfig(
|
||||
hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False)
|
||||
)
|
||||
await conf.async_initialize()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert async_get_entity_settings(hass, "light.state_only") == {
|
||||
"cloud.alexa": {"should_expose": True}
|
||||
}
|
||||
assert async_get_entity_settings(hass, entity_migrated.entity_id) == {
|
||||
"cloud.alexa": {"should_expose": True}
|
||||
}
|
||||
|
||||
|
||||
async def test_alexa_config_migrate_expose_entity_prefs_v2_exposed(
|
||||
hass: HomeAssistant,
|
||||
cloud_prefs: CloudPreferences,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test migrating Alexa entity config from v2 to v3 when an entity is exposed."""
|
||||
hass.state = CoreState.starting
|
||||
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set("light.state_only", "on")
|
||||
entity_migrated = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"test",
|
||||
"light_migrated",
|
||||
suggested_object_id="migrated",
|
||||
)
|
||||
await cloud_prefs.async_update(
|
||||
alexa_enabled=True,
|
||||
alexa_report_state=False,
|
||||
alexa_settings_version=2,
|
||||
)
|
||||
expose_entity(hass, "light.state_only", False)
|
||||
expose_entity(hass, entity_migrated.entity_id, True)
|
||||
|
||||
cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS]["light.state_only"] = {
|
||||
PREF_SHOULD_EXPOSE: True
|
||||
}
|
||||
cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS][entity_migrated.entity_id] = {
|
||||
PREF_SHOULD_EXPOSE: True
|
||||
}
|
||||
conf = alexa_config.CloudAlexaConfig(
|
||||
hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False)
|
||||
)
|
||||
await conf.async_initialize()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert async_get_entity_settings(hass, "light.state_only") == {
|
||||
"cloud.alexa": {"should_expose": False}
|
||||
}
|
||||
assert async_get_entity_settings(hass, entity_migrated.entity_id) == {
|
||||
"cloud.alexa": {"should_expose": True}
|
||||
}
|
||||
|
||||
|
||||
async def test_alexa_config_migrate_expose_entity_prefs_default_none(
|
||||
hass: HomeAssistant,
|
||||
cloud_prefs: CloudPreferences,
|
||||
|
@@ -483,10 +483,12 @@ async def test_google_handle_logout(
|
||||
assert len(mock_enable.return_value.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("google_settings_version", [1, 2])
|
||||
async def test_google_config_migrate_expose_entity_prefs(
|
||||
hass: HomeAssistant,
|
||||
cloud_prefs: CloudPreferences,
|
||||
entity_registry: er.EntityRegistry,
|
||||
google_settings_version: int,
|
||||
) -> None:
|
||||
"""Test migrating Google entity config."""
|
||||
hass.state = CoreState.starting
|
||||
@@ -540,7 +542,7 @@ async def test_google_config_migrate_expose_entity_prefs(
|
||||
await cloud_prefs.async_update(
|
||||
google_enabled=True,
|
||||
google_report_state=False,
|
||||
google_settings_version=1,
|
||||
google_settings_version=google_settings_version,
|
||||
)
|
||||
expose_entity(hass, entity_migrated.entity_id, False)
|
||||
|
||||
@@ -596,6 +598,100 @@ async def test_google_config_migrate_expose_entity_prefs(
|
||||
}
|
||||
|
||||
|
||||
async def test_google_config_migrate_expose_entity_prefs_v2_no_exposed(
|
||||
hass: HomeAssistant,
|
||||
cloud_prefs: CloudPreferences,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test migrating Google entity config from v2 to v3 when no entity is exposed."""
|
||||
hass.state = CoreState.starting
|
||||
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set("light.state_only", "on")
|
||||
entity_migrated = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"test",
|
||||
"light_migrated",
|
||||
suggested_object_id="migrated",
|
||||
)
|
||||
await cloud_prefs.async_update(
|
||||
google_enabled=True,
|
||||
google_report_state=False,
|
||||
google_settings_version=2,
|
||||
)
|
||||
expose_entity(hass, "light.state_only", False)
|
||||
expose_entity(hass, entity_migrated.entity_id, False)
|
||||
|
||||
cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS]["light.state_only"] = {
|
||||
PREF_SHOULD_EXPOSE: True
|
||||
}
|
||||
cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS][entity_migrated.entity_id] = {
|
||||
PREF_SHOULD_EXPOSE: True
|
||||
}
|
||||
conf = CloudGoogleConfig(
|
||||
hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False)
|
||||
)
|
||||
await conf.async_initialize()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert async_get_entity_settings(hass, "light.state_only") == {
|
||||
"cloud.google_assistant": {"should_expose": True}
|
||||
}
|
||||
assert async_get_entity_settings(hass, entity_migrated.entity_id) == {
|
||||
"cloud.google_assistant": {"should_expose": True}
|
||||
}
|
||||
|
||||
|
||||
async def test_google_config_migrate_expose_entity_prefs_v2_exposed(
|
||||
hass: HomeAssistant,
|
||||
cloud_prefs: CloudPreferences,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test migrating Google entity config from v2 to v3 when an entity is exposed."""
|
||||
hass.state = CoreState.starting
|
||||
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set("light.state_only", "on")
|
||||
entity_migrated = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"test",
|
||||
"light_migrated",
|
||||
suggested_object_id="migrated",
|
||||
)
|
||||
await cloud_prefs.async_update(
|
||||
google_enabled=True,
|
||||
google_report_state=False,
|
||||
google_settings_version=2,
|
||||
)
|
||||
expose_entity(hass, "light.state_only", False)
|
||||
expose_entity(hass, entity_migrated.entity_id, True)
|
||||
|
||||
cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS]["light.state_only"] = {
|
||||
PREF_SHOULD_EXPOSE: True
|
||||
}
|
||||
cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS][entity_migrated.entity_id] = {
|
||||
PREF_SHOULD_EXPOSE: True
|
||||
}
|
||||
conf = CloudGoogleConfig(
|
||||
hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False)
|
||||
)
|
||||
await conf.async_initialize()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert async_get_entity_settings(hass, "light.state_only") == {
|
||||
"cloud.google_assistant": {"should_expose": False}
|
||||
}
|
||||
assert async_get_entity_settings(hass, entity_migrated.entity_id) == {
|
||||
"cloud.google_assistant": {"should_expose": True}
|
||||
}
|
||||
|
||||
|
||||
async def test_google_config_migrate_expose_entity_prefs_default_none(
|
||||
hass: HomeAssistant,
|
||||
cloud_prefs: CloudPreferences,
|
||||
|
@@ -2,8 +2,9 @@
|
||||
import asyncio
|
||||
from http import HTTPStatus
|
||||
|
||||
import aiohttp
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
from homeassistant.components.rest import DOMAIN
|
||||
from homeassistant.components.rest.switch import (
|
||||
@@ -45,7 +46,6 @@ from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from tests.common import assert_setup_component, async_fire_time_changed
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
NAME = "foo"
|
||||
DEVICE_CLASS = SwitchDeviceClass.SWITCH
|
||||
@@ -75,13 +75,13 @@ async def test_setup_missing_schema(
|
||||
assert "Invalid config for [switch.rest]: invalid url" in caplog.text
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_failed_connect(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test setup when connection error occurs."""
|
||||
aioclient_mock.get(RESOURCE, exc=aiohttp.ClientError)
|
||||
respx.get(RESOURCE).mock(side_effect=asyncio.TimeoutError())
|
||||
config = {SWITCH_DOMAIN: {CONF_PLATFORM: DOMAIN, CONF_RESOURCE: RESOURCE}}
|
||||
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
@@ -89,13 +89,13 @@ async def test_setup_failed_connect(
|
||||
assert "No route to resource/endpoint" in caplog.text
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_timeout(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test setup when connection timeout occurs."""
|
||||
aioclient_mock.get(RESOURCE, exc=asyncio.TimeoutError())
|
||||
respx.get(RESOURCE).mock(side_effect=asyncio.TimeoutError())
|
||||
config = {SWITCH_DOMAIN: {CONF_PLATFORM: DOMAIN, CONF_RESOURCE: RESOURCE}}
|
||||
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
@@ -103,23 +103,21 @@ async def test_setup_timeout(
|
||||
assert "No route to resource/endpoint" in caplog.text
|
||||
|
||||
|
||||
async def test_setup_minimum(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_setup_minimum(hass: HomeAssistant) -> None:
|
||||
"""Test setup with minimum configuration."""
|
||||
aioclient_mock.get(RESOURCE, status=HTTPStatus.OK)
|
||||
route = respx.get(RESOURCE) % HTTPStatus.OK
|
||||
config = {SWITCH_DOMAIN: {CONF_PLATFORM: DOMAIN, CONF_RESOURCE: RESOURCE}}
|
||||
with assert_setup_component(1, SWITCH_DOMAIN):
|
||||
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
assert aioclient_mock.call_count == 1
|
||||
assert route.call_count == 1
|
||||
|
||||
|
||||
async def test_setup_query_params(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_setup_query_params(hass: HomeAssistant) -> None:
|
||||
"""Test setup with query params."""
|
||||
aioclient_mock.get("http://localhost/?search=something", status=HTTPStatus.OK)
|
||||
route = respx.get("http://localhost/?search=something") % HTTPStatus.OK
|
||||
config = {
|
||||
SWITCH_DOMAIN: {
|
||||
CONF_PLATFORM: DOMAIN,
|
||||
@@ -131,12 +129,13 @@ async def test_setup_query_params(
|
||||
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.call_count == 1
|
||||
assert route.call_count == 1
|
||||
|
||||
|
||||
async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None:
|
||||
@respx.mock
|
||||
async def test_setup(hass: HomeAssistant) -> None:
|
||||
"""Test setup with valid configuration."""
|
||||
aioclient_mock.get(RESOURCE, status=HTTPStatus.OK)
|
||||
route = respx.get(RESOURCE) % HTTPStatus.OK
|
||||
config = {
|
||||
SWITCH_DOMAIN: {
|
||||
CONF_PLATFORM: DOMAIN,
|
||||
@@ -149,16 +148,15 @@ async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -
|
||||
}
|
||||
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
assert aioclient_mock.call_count == 1
|
||||
assert route.call_count == 1
|
||||
assert_setup_component(1, SWITCH_DOMAIN)
|
||||
|
||||
|
||||
async def test_setup_with_state_resource(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_setup_with_state_resource(hass: HomeAssistant) -> None:
|
||||
"""Test setup with valid configuration."""
|
||||
aioclient_mock.get(RESOURCE, status=HTTPStatus.NOT_FOUND)
|
||||
aioclient_mock.get("http://localhost/state", status=HTTPStatus.OK)
|
||||
respx.get(RESOURCE) % HTTPStatus.NOT_FOUND
|
||||
route = respx.get("http://localhost/state") % HTTPStatus.OK
|
||||
config = {
|
||||
SWITCH_DOMAIN: {
|
||||
CONF_PLATFORM: DOMAIN,
|
||||
@@ -172,15 +170,14 @@ async def test_setup_with_state_resource(
|
||||
}
|
||||
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
assert aioclient_mock.call_count == 1
|
||||
assert route.call_count == 1
|
||||
assert_setup_component(1, SWITCH_DOMAIN)
|
||||
|
||||
|
||||
async def test_setup_with_templated_headers_params(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_setup_with_templated_headers_params(hass: HomeAssistant) -> None:
|
||||
"""Test setup with valid configuration."""
|
||||
aioclient_mock.get(RESOURCE, status=HTTPStatus.OK)
|
||||
route = respx.get(RESOURCE) % HTTPStatus.OK
|
||||
config = {
|
||||
SWITCH_DOMAIN: {
|
||||
CONF_PLATFORM: DOMAIN,
|
||||
@@ -198,21 +195,21 @@ async def test_setup_with_templated_headers_params(
|
||||
}
|
||||
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
assert aioclient_mock.call_count == 1
|
||||
assert aioclient_mock.mock_calls[-1][3].get("Accept") == CONTENT_TYPE_JSON
|
||||
assert aioclient_mock.mock_calls[-1][3].get("User-Agent") == "Mozilla/5.0"
|
||||
assert aioclient_mock.mock_calls[-1][1].query["start"] == "0"
|
||||
assert aioclient_mock.mock_calls[-1][1].query["end"] == "5"
|
||||
assert route.call_count == 1
|
||||
last_call = route.calls[-1]
|
||||
last_request: httpx.Request = last_call.request
|
||||
assert last_request.headers.get("Accept") == CONTENT_TYPE_JSON
|
||||
assert last_request.headers.get("User-Agent") == "Mozilla/5.0"
|
||||
assert last_request.url.params["start"] == "0"
|
||||
assert last_request.url.params["end"] == "5"
|
||||
assert_setup_component(1, SWITCH_DOMAIN)
|
||||
|
||||
|
||||
# Tests for REST switch platform.
|
||||
|
||||
|
||||
async def _async_setup_test_switch(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
aioclient_mock.get(RESOURCE, status=HTTPStatus.OK)
|
||||
async def _async_setup_test_switch(hass: HomeAssistant) -> None:
|
||||
respx.get(RESOURCE) % HTTPStatus.OK
|
||||
|
||||
headers = {"Content-type": CONTENT_TYPE_JSON}
|
||||
config = {
|
||||
@@ -223,51 +220,48 @@ async def _async_setup_test_switch(
|
||||
CONF_STATE_RESOURCE: STATE_RESOURCE,
|
||||
CONF_HEADERS: headers,
|
||||
}
|
||||
|
||||
assert await async_setup_component(hass, SWITCH_DOMAIN, {SWITCH_DOMAIN: config})
|
||||
await hass.async_block_till_done()
|
||||
assert_setup_component(1, SWITCH_DOMAIN)
|
||||
|
||||
assert hass.states.get("switch.foo").state == STATE_UNKNOWN
|
||||
aioclient_mock.clear_requests()
|
||||
respx.reset()
|
||||
|
||||
|
||||
async def test_name(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None:
|
||||
@respx.mock
|
||||
async def test_name(hass: HomeAssistant) -> None:
|
||||
"""Test the name."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
state = hass.states.get("switch.foo")
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == NAME
|
||||
|
||||
|
||||
async def test_device_class(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_device_class(hass: HomeAssistant) -> None:
|
||||
"""Test the device class."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
state = hass.states.get("switch.foo")
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS
|
||||
|
||||
|
||||
async def test_is_on_before_update(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_is_on_before_update(hass: HomeAssistant) -> None:
|
||||
"""Test is_on in initial state."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
state = hass.states.get("switch.foo")
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_turn_on_success(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_turn_on_success(hass: HomeAssistant) -> None:
|
||||
"""Test turn_on."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.post(RESOURCE, status=HTTPStatus.OK)
|
||||
aioclient_mock.get(RESOURCE, exc=aiohttp.ClientError)
|
||||
route = respx.post(RESOURCE) % HTTPStatus.OK
|
||||
respx.get(RESOURCE).mock(side_effect=httpx.RequestError)
|
||||
assert await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
@@ -276,17 +270,18 @@ async def test_turn_on_success(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.mock_calls[-2][2].decode() == "ON"
|
||||
last_call = route.calls[-1]
|
||||
last_request: httpx.Request = last_call.request
|
||||
assert last_request.content.decode() == "ON"
|
||||
assert hass.states.get("switch.foo").state == STATE_ON
|
||||
|
||||
|
||||
async def test_turn_on_status_not_ok(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_turn_on_status_not_ok(hass: HomeAssistant) -> None:
|
||||
"""Test turn_on when error status returned."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.post(RESOURCE, status=HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
route = respx.post(RESOURCE) % HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
assert await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
@@ -295,17 +290,18 @@ async def test_turn_on_status_not_ok(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][2].decode() == "ON"
|
||||
last_call = route.calls[-1]
|
||||
last_request: httpx.Request = last_call.request
|
||||
assert last_request.content.decode() == "ON"
|
||||
assert hass.states.get("switch.foo").state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_turn_on_timeout(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_turn_on_timeout(hass: HomeAssistant) -> None:
|
||||
"""Test turn_on when timeout occurs."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.post(RESOURCE, status=HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
respx.post(RESOURCE) % HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
assert await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
@@ -317,14 +313,13 @@ async def test_turn_on_timeout(
|
||||
assert hass.states.get("switch.foo").state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_turn_off_success(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_turn_off_success(hass: HomeAssistant) -> None:
|
||||
"""Test turn_off."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.post(RESOURCE, status=HTTPStatus.OK)
|
||||
aioclient_mock.get(RESOURCE, exc=aiohttp.ClientError)
|
||||
route = respx.post(RESOURCE) % HTTPStatus.OK
|
||||
respx.get(RESOURCE).mock(side_effect=httpx.RequestError)
|
||||
assert await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@@ -333,18 +328,19 @@ async def test_turn_off_success(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.mock_calls[-2][2].decode() == "OFF"
|
||||
last_call = route.calls[-1]
|
||||
last_request: httpx.Request = last_call.request
|
||||
assert last_request.content.decode() == "OFF"
|
||||
|
||||
assert hass.states.get("switch.foo").state == STATE_OFF
|
||||
|
||||
|
||||
async def test_turn_off_status_not_ok(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_turn_off_status_not_ok(hass: HomeAssistant) -> None:
|
||||
"""Test turn_off when error status returned."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.post(RESOURCE, status=HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
route = respx.post(RESOURCE) % HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
assert await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@@ -353,18 +349,19 @@ async def test_turn_off_status_not_ok(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][2].decode() == "OFF"
|
||||
last_call = route.calls[-1]
|
||||
last_request: httpx.Request = last_call.request
|
||||
assert last_request.content.decode() == "OFF"
|
||||
|
||||
assert hass.states.get("switch.foo").state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_turn_off_timeout(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_turn_off_timeout(hass: HomeAssistant) -> None:
|
||||
"""Test turn_off when timeout occurs."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.post(RESOURCE, exc=asyncio.TimeoutError())
|
||||
respx.post(RESOURCE).mock(side_effect=asyncio.TimeoutError())
|
||||
assert await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@@ -376,64 +373,59 @@ async def test_turn_off_timeout(
|
||||
assert hass.states.get("switch.foo").state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_update_when_on(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_update_when_on(hass: HomeAssistant) -> None:
|
||||
"""Test update when switch is on."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.get(RESOURCE, text="ON")
|
||||
respx.get(RESOURCE).respond(text="ON")
|
||||
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("switch.foo").state == STATE_ON
|
||||
|
||||
|
||||
async def test_update_when_off(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_update_when_off(hass: HomeAssistant) -> None:
|
||||
"""Test update when switch is off."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.get(RESOURCE, text="OFF")
|
||||
respx.get(RESOURCE).respond(text="OFF")
|
||||
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("switch.foo").state == STATE_OFF
|
||||
|
||||
|
||||
async def test_update_when_unknown(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_update_when_unknown(hass: HomeAssistant) -> None:
|
||||
"""Test update when unknown status returned."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.get(RESOURCE, text="unknown status")
|
||||
respx.get(RESOURCE).respond(text="unknown status")
|
||||
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("switch.foo").state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_update_timeout(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_update_timeout(hass: HomeAssistant) -> None:
|
||||
"""Test update when timeout occurs."""
|
||||
await _async_setup_test_switch(hass, aioclient_mock)
|
||||
await _async_setup_test_switch(hass)
|
||||
|
||||
aioclient_mock.get(RESOURCE, exc=asyncio.TimeoutError())
|
||||
respx.get(RESOURCE).mock(side_effect=asyncio.TimeoutError())
|
||||
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("switch.foo").state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_entity_config(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
@respx.mock
|
||||
async def test_entity_config(hass: HomeAssistant) -> None:
|
||||
"""Test entity configuration."""
|
||||
|
||||
aioclient_mock.get(RESOURCE, status=HTTPStatus.OK)
|
||||
respx.get(RESOURCE) % HTTPStatus.OK
|
||||
config = {
|
||||
SWITCH_DOMAIN: {
|
||||
# REST configuration
|
||||
|
@@ -102,7 +102,7 @@ INDEXED_SENSOR_CONFIG_2 = {
|
||||
}
|
||||
|
||||
|
||||
NESTED_SENSOR_CONFIG = {
|
||||
NESTED_SENSOR_CONFIG_1 = {
|
||||
"sn": {
|
||||
"Time": "2020-03-03T00:00:00+00:00",
|
||||
"TX23": {
|
||||
@@ -119,6 +119,17 @@ NESTED_SENSOR_CONFIG = {
|
||||
}
|
||||
}
|
||||
|
||||
NESTED_SENSOR_CONFIG_2 = {
|
||||
"sn": {
|
||||
"Time": "2023-01-27T11:04:56",
|
||||
"DS18B20": {
|
||||
"Id": "01191ED79190",
|
||||
"Temperature": 2.4,
|
||||
},
|
||||
"TempUnit": "C",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_controlling_state_via_mqtt(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
|
||||
@@ -174,12 +185,59 @@ async def test_controlling_state_via_mqtt(
|
||||
assert state.state == "20.0"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("sensor_config", "entity_ids", "messages", "states"),
|
||||
[
|
||||
(
|
||||
NESTED_SENSOR_CONFIG_1,
|
||||
["sensor.tasmota_tx23_speed_act", "sensor.tasmota_tx23_dir_card"],
|
||||
(
|
||||
'{"TX23":{"Speed":{"Act":"12.3"},"Dir": {"Card": "WSW"}}}',
|
||||
'{"StatusSNS":{"TX23":{"Speed":{"Act":"23.4"},"Dir": {"Card": "ESE"}}}}',
|
||||
),
|
||||
(
|
||||
{
|
||||
"sensor.tasmota_tx23_speed_act": "12.3",
|
||||
"sensor.tasmota_tx23_dir_card": "WSW",
|
||||
},
|
||||
{
|
||||
"sensor.tasmota_tx23_speed_act": "23.4",
|
||||
"sensor.tasmota_tx23_dir_card": "ESE",
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
NESTED_SENSOR_CONFIG_2,
|
||||
["sensor.tasmota_ds18b20_temperature", "sensor.tasmota_ds18b20_id"],
|
||||
(
|
||||
'{"DS18B20":{"Id": "01191ED79190","Temperature": 12.3}}',
|
||||
'{"StatusSNS":{"DS18B20":{"Id": "meep","Temperature": 23.4}}}',
|
||||
),
|
||||
(
|
||||
{
|
||||
"sensor.tasmota_ds18b20_temperature": "12.3",
|
||||
"sensor.tasmota_ds18b20_id": "01191ED79190",
|
||||
},
|
||||
{
|
||||
"sensor.tasmota_ds18b20_temperature": "23.4",
|
||||
"sensor.tasmota_ds18b20_id": "meep",
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_nested_sensor_state_via_mqtt(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock: MqttMockHAClient,
|
||||
setup_tasmota,
|
||||
sensor_config,
|
||||
entity_ids,
|
||||
messages,
|
||||
states,
|
||||
) -> None:
|
||||
"""Test state update via MQTT."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
sensor_config = copy.deepcopy(NESTED_SENSOR_CONFIG)
|
||||
sensor_config = copy.deepcopy(sensor_config)
|
||||
mac = config["mac"]
|
||||
|
||||
async_fire_mqtt_message(
|
||||
@@ -195,31 +253,29 @@ async def test_nested_sensor_state_via_mqtt(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.tasmota_tx23_speed_act")
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "unavailable"
|
||||
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
|
||||
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("sensor.tasmota_tx23_speed_act")
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
|
||||
# Test periodic state update
|
||||
async_fire_mqtt_message(
|
||||
hass, "tasmota_49A3BC/tele/SENSOR", '{"TX23":{"Speed":{"Act":"12.3"}}}'
|
||||
)
|
||||
state = hass.states.get("sensor.tasmota_tx23_speed_act")
|
||||
assert state.state == "12.3"
|
||||
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/SENSOR", messages[0])
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == states[0][entity_id]
|
||||
|
||||
# Test polled state update
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"tasmota_49A3BC/stat/STATUS10",
|
||||
'{"StatusSNS":{"TX23":{"Speed":{"Act":"23.4"}}}}',
|
||||
)
|
||||
state = hass.states.get("sensor.tasmota_tx23_speed_act")
|
||||
assert state.state == "23.4"
|
||||
async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/STATUS10", messages[1])
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == states[1][entity_id]
|
||||
|
||||
|
||||
async def test_indexed_sensor_state_via_mqtt(
|
||||
@@ -728,7 +784,7 @@ async def test_nested_sensor_attributes(
|
||||
) -> None:
|
||||
"""Test correct attributes for sensors."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
sensor_config = copy.deepcopy(NESTED_SENSOR_CONFIG)
|
||||
sensor_config = copy.deepcopy(NESTED_SENSOR_CONFIG_1)
|
||||
mac = config["mac"]
|
||||
|
||||
async_fire_mqtt_message(
|
||||
@@ -754,7 +810,7 @@ async def test_nested_sensor_attributes(
|
||||
assert state.attributes.get("device_class") is None
|
||||
assert state.attributes.get("friendly_name") == "Tasmota TX23 Dir Avg"
|
||||
assert state.attributes.get("icon") is None
|
||||
assert state.attributes.get("unit_of_measurement") == " "
|
||||
assert state.attributes.get("unit_of_measurement") is None
|
||||
|
||||
|
||||
async def test_indexed_sensor_attributes(
|
||||
|
@@ -2,7 +2,11 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from transmission_rpc.error import TransmissionError
|
||||
from transmission_rpc.error import (
|
||||
TransmissionAuthError,
|
||||
TransmissionConnectError,
|
||||
TransmissionError,
|
||||
)
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import transmission
|
||||
@@ -125,7 +129,7 @@ async def test_error_on_wrong_credentials(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
mock_api.side_effect = TransmissionError("401: Unauthorized")
|
||||
mock_api.side_effect = TransmissionAuthError()
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
MOCK_CONFIG_DATA,
|
||||
@@ -137,6 +141,21 @@ async def test_error_on_wrong_credentials(
|
||||
}
|
||||
|
||||
|
||||
async def test_unexpected_error(hass: HomeAssistant, mock_api: MagicMock) -> None:
|
||||
"""Test we handle unexpected error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
mock_api.side_effect = TransmissionError()
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
MOCK_CONFIG_DATA,
|
||||
)
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_error_on_connection_failure(
|
||||
hass: HomeAssistant, mock_api: MagicMock
|
||||
) -> None:
|
||||
@@ -145,7 +164,7 @@ async def test_error_on_connection_failure(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
mock_api.side_effect = TransmissionError("111: Connection refused")
|
||||
mock_api.side_effect = TransmissionConnectError()
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
MOCK_CONFIG_DATA,
|
||||
@@ -213,7 +232,7 @@ async def test_reauth_failed(hass: HomeAssistant, mock_api: MagicMock) -> None:
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
assert result["description_placeholders"] == {"username": "user"}
|
||||
|
||||
mock_api.side_effect = TransmissionError("401: Unauthorized")
|
||||
mock_api.side_effect = TransmissionAuthError()
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -248,7 +267,7 @@ async def test_reauth_failed_connection_error(
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
assert result["description_placeholders"] == {"username": "user"}
|
||||
|
||||
mock_api.side_effect = TransmissionError("111: Connection refused")
|
||||
mock_api.side_effect = TransmissionConnectError()
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
|
@@ -3,7 +3,11 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from transmission_rpc.error import TransmissionError
|
||||
from transmission_rpc.error import (
|
||||
TransmissionAuthError,
|
||||
TransmissionConnectError,
|
||||
TransmissionError,
|
||||
)
|
||||
|
||||
from homeassistant.components.transmission.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
@@ -40,7 +44,7 @@ async def test_setup_failed_connection_error(
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
mock_api.side_effect = TransmissionError("111: Connection refused")
|
||||
mock_api.side_effect = TransmissionConnectError()
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state == ConfigEntryState.SETUP_RETRY
|
||||
@@ -54,7 +58,21 @@ async def test_setup_failed_auth_error(
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
mock_api.side_effect = TransmissionError("401: Unauthorized")
|
||||
mock_api.side_effect = TransmissionAuthError()
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state == ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
async def test_setup_failed_unexpected_error(
|
||||
hass: HomeAssistant, mock_api: MagicMock
|
||||
) -> None:
|
||||
"""Test integration failed due to unexpected error."""
|
||||
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
mock_api.side_effect = TransmissionError()
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state == ConfigEntryState.SETUP_ERROR
|
||||
|
@@ -1,11 +1,13 @@
|
||||
"""Test ZHA Core cluster handlers."""
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
import math
|
||||
from unittest import mock
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
import zigpy.device
|
||||
import zigpy.endpoint
|
||||
from zigpy.endpoint import Endpoint as ZigpyEndpoint
|
||||
import zigpy.profiles.zha
|
||||
@@ -791,3 +793,41 @@ async def test_configure_reporting(hass: HomeAssistant, endpoint) -> None:
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def test_invalid_cluster_handler(hass: HomeAssistant, caplog) -> None:
|
||||
"""Test setting up a cluster handler that fails to match properly."""
|
||||
|
||||
class TestZigbeeClusterHandler(cluster_handlers.ClusterHandler):
|
||||
REPORT_CONFIG = (
|
||||
cluster_handlers.AttrReportConfig(attr="missing_attr", config=(1, 60, 1)),
|
||||
)
|
||||
|
||||
mock_device = mock.AsyncMock(spec_set=zigpy.device.Device)
|
||||
zigpy_ep = zigpy.endpoint.Endpoint(mock_device, endpoint_id=1)
|
||||
|
||||
cluster = zigpy_ep.add_input_cluster(zigpy.zcl.clusters.lighting.Color.cluster_id)
|
||||
cluster.configure_reporting_multiple = AsyncMock(
|
||||
spec_set=cluster.configure_reporting_multiple,
|
||||
return_value=[
|
||||
foundation.ConfigureReportingResponseRecord(
|
||||
status=foundation.Status.SUCCESS
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
mock_zha_device = mock.AsyncMock(spec_set=ZHADevice)
|
||||
zha_endpoint = Endpoint(zigpy_ep, mock_zha_device)
|
||||
|
||||
# The cluster handler throws an error when matching this cluster
|
||||
with pytest.raises(KeyError):
|
||||
TestZigbeeClusterHandler(cluster, zha_endpoint)
|
||||
|
||||
# And one is also logged at runtime
|
||||
with patch.dict(
|
||||
registries.ZIGBEE_CLUSTER_HANDLER_REGISTRY,
|
||||
{cluster.cluster_id: TestZigbeeClusterHandler},
|
||||
), caplog.at_level(logging.WARNING):
|
||||
zha_endpoint.add_all_cluster_handlers()
|
||||
|
||||
assert "missing_attr" in caplog.text
|
||||
|
Reference in New Issue
Block a user