Merge branch 'dev' into dedupe_event_type

This commit is contained in:
J. Nick Koston
2023-03-10 11:31:15 -10:00
committed by GitHub
74 changed files with 1313 additions and 254 deletions

View File

@@ -395,7 +395,8 @@ omit =
homeassistant/components/fritzbox_callmonitor/__init__.py homeassistant/components/fritzbox_callmonitor/__init__.py
homeassistant/components/fritzbox_callmonitor/base.py homeassistant/components/fritzbox_callmonitor/base.py
homeassistant/components/fritzbox_callmonitor/sensor.py homeassistant/components/fritzbox_callmonitor/sensor.py
homeassistant/components/frontier_silicon/const.py homeassistant/components/frontier_silicon/__init__.py
homeassistant/components/frontier_silicon/browse_media.py
homeassistant/components/frontier_silicon/media_player.py homeassistant/components/frontier_silicon/media_player.py
homeassistant/components/futurenow/light.py homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py homeassistant/components/garadget/cover.py

View File

@@ -198,7 +198,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2022.11.0 uses: home-assistant/builder@2023.03.0
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \
@@ -276,7 +276,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2022.11.0 uses: home-assistant/builder@2023.03.0
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \

View File

@@ -401,6 +401,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/frontend/ @home-assistant/frontend /homeassistant/components/frontend/ @home-assistant/frontend
/tests/components/frontend/ @home-assistant/frontend /tests/components/frontend/ @home-assistant/frontend
/homeassistant/components/frontier_silicon/ @wlcrs /homeassistant/components/frontier_silicon/ @wlcrs
/tests/components/frontier_silicon/ @wlcrs
/homeassistant/components/fully_kiosk/ @cgarwood /homeassistant/components/fully_kiosk/ @cgarwood
/tests/components/fully_kiosk/ @cgarwood /tests/components/fully_kiosk/ @cgarwood
/homeassistant/components/garages_amsterdam/ @klaasnicolaas /homeassistant/components/garages_amsterdam/ @klaasnicolaas

View File

@@ -1 +1,45 @@
"""The frontier_silicon component.""" """The Frontier Silicon integration."""
from __future__ import annotations
import logging
from afsapi import AFSAPI, ConnectionError as FSConnectionError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from .const import CONF_PIN, CONF_WEBFSAPI_URL, DOMAIN
PLATFORMS = [Platform.MEDIA_PLAYER]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Frontier Silicon from a config entry."""
webfsapi_url = entry.data[CONF_WEBFSAPI_URL]
pin = entry.data[CONF_PIN]
afsapi = AFSAPI(webfsapi_url, pin)
try:
await afsapi.get_power()
except FSConnectionError as exception:
raise PlatformNotReady from exception
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = afsapi
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@@ -0,0 +1,178 @@
"""Config flow for Frontier Silicon Media Player integration."""
from __future__ import annotations
import logging
from typing import Any
from afsapi import AFSAPI, ConnectionError as FSConnectionError, InvalidPinException
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
from homeassistant.data_entry_flow import FlowResult
from .const import CONF_PIN, CONF_WEBFSAPI_URL, DEFAULT_PIN, DEFAULT_PORT, DOMAIN
_LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
}
)
STEP_DEVICE_CONFIG_DATA_SCHEMA = vol.Schema(
{
vol.Required(
CONF_PIN,
default=DEFAULT_PIN,
): str,
}
)
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Frontier Silicon Media Player."""
VERSION = 1
def __init__(self) -> None:
"""Initialize flow."""
self._webfsapi_url: str | None = None
self._name: str | None = None
self._unique_id: str | None = None
async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult:
"""Handle the import of legacy configuration.yaml entries."""
device_url = f"http://{import_info[CONF_HOST]}:{import_info[CONF_PORT]}/device"
try:
self._webfsapi_url = await AFSAPI.get_webfsapi_endpoint(device_url)
except FSConnectionError:
return self.async_abort(reason="cannot_connect")
except Exception as exception: # pylint: disable=broad-except
_LOGGER.exception(exception)
return self.async_abort(reason="unknown")
try:
afsapi = AFSAPI(self._webfsapi_url, import_info[CONF_PIN])
self._unique_id = await afsapi.get_radio_id()
except FSConnectionError:
return self.async_abort(reason="cannot_connect")
except InvalidPinException:
return self.async_abort(reason="invalid_auth")
except Exception as exception: # pylint: disable=broad-except
_LOGGER.exception(exception)
return self.async_abort(reason="unknown")
await self.async_set_unique_id(self._unique_id, raise_on_progress=False)
self._abort_if_unique_id_configured()
self._name = import_info[CONF_NAME] or "Radio"
return await self._create_entry(pin=import_info[CONF_PIN])
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step of manual configuration."""
errors = {}
if user_input:
device_url = (
f"http://{user_input[CONF_HOST]}:{user_input[CONF_PORT]}/device"
)
try:
self._webfsapi_url = await AFSAPI.get_webfsapi_endpoint(device_url)
except FSConnectionError:
errors["base"] = "cannot_connect"
except Exception as exception: # pylint: disable=broad-except
_LOGGER.exception(exception)
errors["base"] = "unknown"
else:
return await self._async_step_device_config_if_needed()
data_schema = self.add_suggested_values_to_schema(
STEP_USER_DATA_SCHEMA, user_input
)
return self.async_show_form(
step_id="user", data_schema=data_schema, errors=errors
)
async def _async_step_device_config_if_needed(self) -> FlowResult:
"""Most users will not have changed the default PIN on their radio.
We try to use this default PIN, and only if this fails ask for it via `async_step_device_config`
"""
try:
# try to login with default pin
afsapi = AFSAPI(self._webfsapi_url, DEFAULT_PIN)
self._name = await afsapi.get_friendly_name()
except InvalidPinException:
# Ask for a PIN
return await self.async_step_device_config()
self.context["title_placeholders"] = {"name": self._name}
self._unique_id = await afsapi.get_radio_id()
await self.async_set_unique_id(self._unique_id)
self._abort_if_unique_id_configured()
return await self._create_entry()
async def async_step_device_config(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle device configuration step.
We ask for the PIN in this step.
"""
assert self._webfsapi_url is not None
if user_input is None:
return self.async_show_form(
step_id="device_config", data_schema=STEP_DEVICE_CONFIG_DATA_SCHEMA
)
errors = {}
try:
afsapi = AFSAPI(self._webfsapi_url, user_input[CONF_PIN])
self._name = await afsapi.get_friendly_name()
except FSConnectionError:
errors["base"] = "cannot_connect"
except InvalidPinException:
errors["base"] = "invalid_auth"
except Exception as exception: # pylint: disable=broad-except
_LOGGER.exception(exception)
errors["base"] = "unknown"
else:
self._unique_id = await afsapi.get_radio_id()
await self.async_set_unique_id(self._unique_id)
self._abort_if_unique_id_configured()
return await self._create_entry(pin=user_input[CONF_PIN])
data_schema = self.add_suggested_values_to_schema(
STEP_DEVICE_CONFIG_DATA_SCHEMA, user_input
)
return self.async_show_form(
step_id="device_config",
data_schema=data_schema,
errors=errors,
)
async def _create_entry(self, pin: str | None = None) -> FlowResult:
"""Create the entry."""
assert self._name is not None
assert self._webfsapi_url is not None
data = {CONF_WEBFSAPI_URL: self._webfsapi_url, CONF_PIN: pin or DEFAULT_PIN}
return self.async_create_entry(title=self._name, data=data)

View File

@@ -1,6 +1,9 @@
"""Constants for the Frontier Silicon Media Player integration.""" """Constants for the Frontier Silicon Media Player integration."""
DOMAIN = "frontier_silicon" DOMAIN = "frontier_silicon"
CONF_WEBFSAPI_URL = "webfsapi_url"
CONF_PIN = "pin"
DEFAULT_PIN = "1234" DEFAULT_PIN = "1234"
DEFAULT_PORT = 80 DEFAULT_PORT = 80

View File

@@ -2,6 +2,7 @@
"domain": "frontier_silicon", "domain": "frontier_silicon",
"name": "Frontier Silicon", "name": "Frontier Silicon",
"codeowners": ["@wlcrs"], "codeowners": ["@wlcrs"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/frontier_silicon", "documentation": "https://www.home-assistant.io/integrations/frontier_silicon",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["afsapi==0.2.7"] "requirements": ["afsapi==0.2.7"]

View File

@@ -21,15 +21,17 @@ from homeassistant.components.media_player import (
MediaPlayerState, MediaPlayerState,
MediaType, MediaType,
) )
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .browse_media import browse_node, browse_top_level from .browse_media import browse_node, browse_top_level
from .const import DEFAULT_PIN, DEFAULT_PORT, DOMAIN, MEDIA_CONTENT_ID_PRESET from .const import CONF_PIN, DEFAULT_PIN, DEFAULT_PORT, DOMAIN, MEDIA_CONTENT_ID_PRESET
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -49,7 +51,11 @@ async def async_setup_platform(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None, discovery_info: DiscoveryInfoType | None = None,
) -> None: ) -> None:
"""Set up the Frontier Silicon platform.""" """Set up the Frontier Silicon platform.
YAML is deprecated, and imported automatically.
SSDP discovery is temporarily retained - to be refactor subsequently.
"""
if discovery_info is not None: if discovery_info is not None:
webfsapi_url = await AFSAPI.get_webfsapi_endpoint( webfsapi_url = await AFSAPI.get_webfsapi_endpoint(
discovery_info["ssdp_description"] discovery_info["ssdp_description"]
@@ -61,24 +67,41 @@ async def async_setup_platform(
[AFSAPIDevice(name, afsapi)], [AFSAPIDevice(name, afsapi)],
True, True,
) )
return return
host = config.get(CONF_HOST) ir.async_create_issue(
port = config.get(CONF_PORT) hass,
password = config.get(CONF_PASSWORD) DOMAIN,
name = config.get(CONF_NAME) "remove_yaml",
breaks_in_ha_version="2023.6.0",
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key="removed_yaml",
)
try: await hass.config_entries.flow.async_init(
webfsapi_url = await AFSAPI.get_webfsapi_endpoint( DOMAIN,
f"http://{host}:{port}/device" context={"source": SOURCE_IMPORT},
) data={
except FSConnectionError: CONF_NAME: config.get(CONF_NAME),
_LOGGER.error( CONF_HOST: config.get(CONF_HOST),
"Could not add the FSAPI device at %s:%s -> %s", host, port, password CONF_PORT: config.get(CONF_PORT, DEFAULT_PORT),
) CONF_PIN: config.get(CONF_PASSWORD, DEFAULT_PIN),
return },
afsapi = AFSAPI(webfsapi_url, password) )
async_add_entities([AFSAPIDevice(name, afsapi)], True)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Frontier Silicon entity."""
afsapi: AFSAPI = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities([AFSAPIDevice(config_entry.title, afsapi)], True)
class AFSAPIDevice(MediaPlayerEntity): class AFSAPIDevice(MediaPlayerEntity):

View File

@@ -0,0 +1,35 @@
{
"config": {
"flow_title": "{name}",
"step": {
"user": {
"title": "Frontier Silicon Setup",
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]"
}
},
"device_config": {
"title": "Device Configuration",
"description": "The pin can be found via 'MENU button > Main Menu > System setting > Network > NetRemote PIN setup'",
"data": {
"pin": "[%key:common::config_flow::data::pin%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"issues": {
"removed_yaml": {
"title": "The Frontier Silicon YAML configuration has been removed",
"description": "Configuring Frontier Silicon using YAML has been removed.\n\nYour existing YAML configuration is not used by Home Assistant.\n\nRemove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
}
}
}

View File

@@ -1,19 +1,17 @@
"""The Landis+Gyr Heat Meter integration.""" """The Landis+Gyr Heat Meter integration."""
from __future__ import annotations from __future__ import annotations
from datetime import timedelta
import logging import logging
import ultraheat_api import ultraheat_api
from ultraheat_api.response import HeatMeterResponse
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE, Platform from homeassistant.const import CONF_DEVICE, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_registry import async_migrate_entries from homeassistant.helpers.entity_registry import async_migrate_entries
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN
from .coordinator import UltraheatCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -27,19 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
reader = ultraheat_api.UltraheatReader(entry.data[CONF_DEVICE]) reader = ultraheat_api.UltraheatReader(entry.data[CONF_DEVICE])
api = ultraheat_api.HeatMeterService(reader) api = ultraheat_api.HeatMeterService(reader)
async def async_update_data() -> HeatMeterResponse: coordinator = UltraheatCoordinator(hass, api)
"""Fetch data from the API."""
_LOGGER.debug("Polling on %s", entry.data[CONF_DEVICE])
return await hass.async_add_executor_job(api.read)
# Polling is only daily to prevent battery drain.
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="ultraheat_gateway",
update_method=async_update_data,
update_interval=timedelta(days=1),
)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator

View File

@@ -1,6 +1,9 @@
"""Constants for the Landis+Gyr Heat Meter integration.""" """Constants for the Landis+Gyr Heat Meter integration."""
from datetime import timedelta
DOMAIN = "landisgyr_heat_meter" DOMAIN = "landisgyr_heat_meter"
GJ_TO_MWH = 0.277778 # conversion factor GJ_TO_MWH = 0.277778 # conversion factor
ULTRAHEAT_TIMEOUT = 30 # reading the IR port can take some time ULTRAHEAT_TIMEOUT = 30 # reading the IR port can take some time
POLLING_INTERVAL = timedelta(days=1) # Polling is only daily to prevent battery drain.

View File

@@ -0,0 +1,37 @@
"""Data update coordinator for the ultraheat api."""
import logging
import async_timeout
import serial
from ultraheat_api.response import HeatMeterResponse
from ultraheat_api.service import HeatMeterService
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import POLLING_INTERVAL, ULTRAHEAT_TIMEOUT
_LOGGER = logging.getLogger(__name__)
class UltraheatCoordinator(DataUpdateCoordinator[HeatMeterResponse]):
"""Coordinator for getting data from the ultraheat api."""
def __init__(self, hass: HomeAssistant, api: HeatMeterService) -> None:
"""Initialize my coordinator."""
super().__init__(
hass,
_LOGGER,
name="ultraheat",
update_interval=POLLING_INTERVAL,
)
self.api = api
async def _async_update_data(self) -> HeatMeterResponse:
"""Fetch data from API endpoint."""
try:
async with async_timeout.timeout(ULTRAHEAT_TIMEOUT):
return await self.hass.async_add_executor_job(self.api.read)
except (FileNotFoundError, serial.serialutil.SerialException) as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err

View File

@@ -1112,7 +1112,7 @@ def _migrate_columns_to_timestamp(
result = session.connection().execute( result = session.connection().execute(
text( text(
"UPDATE events set time_fired_ts=" "UPDATE events set time_fired_ts="
"IF(time_fired is NULL,0," "IF(time_fired is NULL or UNIX_TIMESTAMP(time_fired) is NULL,0,"
"UNIX_TIMESTAMP(time_fired)" "UNIX_TIMESTAMP(time_fired)"
") " ") "
"where time_fired_ts is NULL " "where time_fired_ts is NULL "
@@ -1125,7 +1125,7 @@ def _migrate_columns_to_timestamp(
result = session.connection().execute( result = session.connection().execute(
text( text(
"UPDATE states set last_updated_ts=" "UPDATE states set last_updated_ts="
"IF(last_updated is NULL,0," "IF(last_updated is NULL or UNIX_TIMESTAMP(last_updated) is NULL,0,"
"UNIX_TIMESTAMP(last_updated) " "UNIX_TIMESTAMP(last_updated) "
"), " "), "
"last_changed_ts=" "last_changed_ts="
@@ -1201,7 +1201,7 @@ def _migrate_statistics_columns_to_timestamp(
result = session.connection().execute( result = session.connection().execute(
text( text(
f"UPDATE {table} set start_ts=" f"UPDATE {table} set start_ts="
"IF(start is NULL,0," "IF(start is NULL or UNIX_TIMESTAMP(start) is NULL,0,"
"UNIX_TIMESTAMP(start) " "UNIX_TIMESTAMP(start) "
"), " "), "
"created_ts=" "created_ts="

View File

@@ -27,8 +27,14 @@ async def async_get_config_entry_diagnostics(
}, },
"data": { "data": {
"dsl": async_redact_data(dataclasses.asdict(data.dsl.data), TO_REDACT), "dsl": async_redact_data(dataclasses.asdict(data.dsl.data), TO_REDACT),
"ftth": async_redact_data(
dataclasses.asdict(await data.system.box.ftth_get_info()), TO_REDACT
),
"system": async_redact_data( "system": async_redact_data(
dataclasses.asdict(data.system.data), TO_REDACT dataclasses.asdict(data.system.data), TO_REDACT
), ),
"wan": async_redact_data(
dataclasses.asdict(await data.system.box.wan_get_info()), TO_REDACT
),
}, },
} }

View File

@@ -6,6 +6,7 @@ import dataclasses
import logging import logging
from typing import cast from typing import cast
from python_otbr_api.mdns import StateBitmap
from zeroconf import BadTypeInNameException, DNSPointer, ServiceListener, Zeroconf from zeroconf import BadTypeInNameException, DNSPointer, ServiceListener, Zeroconf
from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf
@@ -29,14 +30,15 @@ TYPE_PTR = 12
class ThreadRouterDiscoveryData: class ThreadRouterDiscoveryData:
"""Thread router discovery data.""" """Thread router discovery data."""
addresses: list[str] | None
brand: str | None brand: str | None
extended_pan_id: str | None extended_pan_id: str | None
model_name: str | None model_name: str | None
network_name: str | None network_name: str | None
server: str | None server: str | None
vendor_name: str | None
addresses: list[str] | None
thread_version: str | None thread_version: str | None
unconfigured: bool | None
vendor_name: str | None
def async_discovery_data_from_service( def async_discovery_data_from_service(
@@ -59,15 +61,30 @@ def async_discovery_data_from_service(
server = service.server server = service.server
vendor_name = try_decode(service.properties.get(b"vn")) vendor_name = try_decode(service.properties.get(b"vn"))
thread_version = try_decode(service.properties.get(b"tv")) thread_version = try_decode(service.properties.get(b"tv"))
unconfigured = None
brand = KNOWN_BRANDS.get(vendor_name)
if brand == "homeassistant":
# Attempt to detect incomplete configuration
if (state_bitmap_b := service.properties.get(b"sb")) is not None:
try:
state_bitmap = StateBitmap.from_bytes(state_bitmap_b)
if not state_bitmap.is_active:
unconfigured = True
except ValueError:
_LOGGER.debug("Failed to decode state bitmap in service %s", service)
if service.properties.get(b"at") is None:
unconfigured = True
return ThreadRouterDiscoveryData( return ThreadRouterDiscoveryData(
brand=KNOWN_BRANDS.get(vendor_name), addresses=service.parsed_addresses(),
brand=brand,
extended_pan_id=ext_pan_id.hex() if ext_pan_id is not None else None, extended_pan_id=ext_pan_id.hex() if ext_pan_id is not None else None,
model_name=model_name, model_name=model_name,
network_name=network_name, network_name=network_name,
server=server, server=server,
vendor_name=vendor_name,
addresses=service.parsed_addresses(),
thread_version=thread_version, thread_version=thread_version,
unconfigured=unconfigured,
vendor_name=vendor_name,
) )

View File

@@ -213,3 +213,4 @@ class XiaomiPlugConsumerConnected(BinarySensor, id_suffix="consumer_connected"):
SENSOR_ATTR = "consumer_connected" SENSOR_ATTR = "consumer_connected"
_attr_name: str = "Consumer connected" _attr_name: str = "Consumer connected"
_attr_device_class: BinarySensorDeviceClass = BinarySensorDeviceClass.PLUG

View File

@@ -145,6 +145,7 @@ FLOWS = {
"fritzbox", "fritzbox",
"fritzbox_callmonitor", "fritzbox_callmonitor",
"fronius", "fronius",
"frontier_silicon",
"fully_kiosk", "fully_kiosk",
"garages_amsterdam", "garages_amsterdam",
"gdacs", "gdacs",

View File

@@ -1818,7 +1818,7 @@
"frontier_silicon": { "frontier_silicon": {
"name": "Frontier Silicon", "name": "Frontier Silicon",
"integration_type": "hub", "integration_type": "hub",
"config_flow": false, "config_flow": true,
"iot_class": "local_polling" "iot_class": "local_polling"
}, },
"fully_kiosk": { "fully_kiosk": {

View File

@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, TypeVar, cast
import attr import attr
from homeassistant.backports.enum import StrEnum from homeassistant.backports.enum import StrEnum
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, RequiredParameterMissing from homeassistant.exceptions import HomeAssistantError, RequiredParameterMissing
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
@@ -907,6 +907,13 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, startup_clean) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, startup_clean)
@callback
def _on_homeassistant_stop(event: Event) -> None:
"""Cancel debounced cleanup."""
debounced_cleanup.async_cancel()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _on_homeassistant_stop)
def _normalize_connections(connections: set[tuple[str, str]]) -> set[tuple[str, str]]: def _normalize_connections(connections: set[tuple[str, str]]) -> set[tuple[str, str]]:
"""Normalize connections to ensure we can match mac addresses.""" """Normalize connections to ensure we can match mac addresses."""

View File

@@ -24,11 +24,11 @@ pytest-cov==3.0.0
pytest-freezer==0.4.6 pytest-freezer==0.4.6
pytest-socket==0.5.1 pytest-socket==0.5.1
pytest-test-groups==1.0.3 pytest-test-groups==1.0.3
pytest-sugar==0.9.5 pytest-sugar==0.9.6
pytest-timeout==2.1.0 pytest-timeout==2.1.0
pytest-unordered==0.5.2 pytest-unordered==0.5.2
pytest-picked==0.4.6 pytest-picked==0.4.6
pytest-xdist==2.5.0 pytest-xdist==3.2.0
pytest==7.2.2 pytest==7.2.2
requests_mock==1.10.0 requests_mock==1.10.0
respx==0.20.1 respx==0.20.1

View File

@@ -78,6 +78,9 @@ adguardhome==0.6.1
# homeassistant.components.advantage_air # homeassistant.components.advantage_air
advantage_air==0.4.1 advantage_air==0.4.1
# homeassistant.components.frontier_silicon
afsapi==0.2.7
# homeassistant.components.agent_dvr # homeassistant.components.agent_dvr
agent-py==0.0.23 agent-py==0.0.23

View File

@@ -33,12 +33,12 @@ async def test_form(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) ->
result1["flow_id"], result1["flow_id"],
USER_INPUT, USER_INPUT,
) )
await hass.async_block_till_done()
assert len(aioclient_mock.mock_calls) == 1 assert len(aioclient_mock.mock_calls) == 1
assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result2["title"] == "testname" assert result2["title"] == "testname"
assert result2["data"] == USER_INPUT assert result2["data"] == USER_INPUT
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
# Test Duplicate Config Flow # Test Duplicate Config Flow

View File

@@ -187,11 +187,11 @@ async def test_user_form_one_entry_per_device_allowed(hass: HomeAssistant) -> No
result["flow_id"], result["flow_id"],
{CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"},
) )
await hass.async_block_till_done()
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "already_configured" assert result2["reason"] == "already_configured"
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 0 assert len(mock_setup_entry.mock_calls) == 0

View File

@@ -86,7 +86,7 @@ async def test_name(thermostat) -> None:
assert thermostat.name == "Ecobee" assert thermostat.name == "Ecobee"
async def test_aux_heat_not_supported_by_default(hass): async def test_aux_heat_not_supported_by_default(hass: HomeAssistant) -> None:
"""Default setup should not support Aux heat.""" """Default setup should not support Aux heat."""
await setup_platform(hass, const.Platform.CLIMATE) await setup_platform(hass, const.Platform.CLIMATE)
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
@@ -100,7 +100,7 @@ async def test_aux_heat_not_supported_by_default(hass):
) )
async def test_aux_heat_supported_with_heat_pump(hass): async def test_aux_heat_supported_with_heat_pump(hass: HomeAssistant) -> None:
"""Aux Heat should be supported if thermostat has heatpump.""" """Aux Heat should be supported if thermostat has heatpump."""
mock_get_thermostat = mock.Mock() mock_get_thermostat = mock.Mock()
mock_get_thermostat.return_value = GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP mock_get_thermostat.return_value = GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP
@@ -242,7 +242,7 @@ async def test_extra_state_attributes(ecobee_fixture, thermostat) -> None:
} == thermostat.extra_state_attributes } == thermostat.extra_state_attributes
async def test_is_aux_heat_on(hass): async def test_is_aux_heat_on(hass: HomeAssistant) -> None:
"""Test aux heat property is only enabled for auxHeatOnly.""" """Test aux heat property is only enabled for auxHeatOnly."""
mock_get_thermostat = mock.Mock() mock_get_thermostat = mock.Mock()
mock_get_thermostat.return_value = copy.deepcopy( mock_get_thermostat.return_value = copy.deepcopy(
@@ -255,7 +255,7 @@ async def test_is_aux_heat_on(hass):
assert state.attributes[climate.ATTR_AUX_HEAT] == "on" assert state.attributes[climate.ATTR_AUX_HEAT] == "on"
async def test_is_aux_heat_off(hass): async def test_is_aux_heat_off(hass: HomeAssistant) -> None:
"""Test aux heat property is only enabled for auxHeatOnly.""" """Test aux heat property is only enabled for auxHeatOnly."""
mock_get_thermostat = mock.Mock() mock_get_thermostat = mock.Mock()
mock_get_thermostat.return_value = GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP mock_get_thermostat.return_value = GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP

View File

@@ -1,5 +1,4 @@
"""Tests for the sensors provided by the EnergyZero integration.""" """Tests for the sensors provided by the EnergyZero integration."""
from unittest.mock import MagicMock from unittest.mock import MagicMock
from energyzero import EnergyZeroNoDataError from energyzero import EnergyZeroNoDataError

View File

@@ -33,11 +33,11 @@ async def test_form(hass: HomeAssistant) -> None:
result["flow_id"], result["flow_id"],
{CONF_HOST: "1.1.1.1", CONF_NAME: "test-epson"}, {CONF_HOST: "1.1.1.1", CONF_NAME: "test-epson"},
) )
await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == "test-epson" assert result2["title"] == "test-epson"
assert result2["data"] == {CONF_HOST: "1.1.1.1"} assert result2["data"] == {CONF_HOST: "1.1.1.1"}
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View File

@@ -37,13 +37,13 @@ async def test_form(hass: HomeAssistant) -> None:
"id": "test", "id": "test",
}, },
) )
await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == "Test airport" assert result2["title"] == "Test airport"
assert result2["data"] == { assert result2["data"] == {
"id": "test", "id": "test",
} }
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View File

@@ -0,0 +1 @@
"""Tests for the Frontier Silicon integration."""

View File

@@ -0,0 +1,59 @@
"""Configuration for frontier_silicon tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.frontier_silicon.const import (
CONF_PIN,
CONF_WEBFSAPI_URL,
DOMAIN,
)
from tests.common import MockConfigEntry
@pytest.fixture
def config_entry() -> MockConfigEntry:
"""Create a mock Frontier Silicon config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id="mock_radio_id",
data={CONF_WEBFSAPI_URL: "http://1.1.1.1:80/webfsapi", CONF_PIN: "1234"},
)
@pytest.fixture(autouse=True)
def mock_valid_device_url() -> Generator[None, None, None]:
"""Return a valid webfsapi endpoint."""
with patch(
"afsapi.AFSAPI.get_webfsapi_endpoint",
return_value="http://1.1.1.1:80/webfsapi",
):
yield
@pytest.fixture(autouse=True)
def mock_valid_pin() -> Generator[None, None, None]:
"""Make get_friendly_name return a value, indicating a valid pin."""
with patch(
"afsapi.AFSAPI.get_friendly_name",
return_value="Name of the device",
):
yield
@pytest.fixture(autouse=True)
def mock_radio_id() -> Generator[None, None, None]:
"""Return a valid radio_id."""
with patch("afsapi.AFSAPI.get_radio_id", return_value="mock_radio_id"):
yield
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.frontier_silicon.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@@ -0,0 +1,266 @@
"""Test the Frontier Silicon config flow."""
from unittest.mock import AsyncMock, patch
from afsapi import ConnectionError, InvalidPinException
import pytest
from homeassistant import config_entries
from homeassistant.components.frontier_silicon.const import CONF_WEBFSAPI_URL, DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_import_success(hass: HomeAssistant) -> None:
"""Test successful import."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_HOST: "1.1.1.1",
CONF_PORT: 80,
CONF_PIN: "1234",
CONF_NAME: "Test name",
},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Test name"
assert result["data"] == {
CONF_WEBFSAPI_URL: "http://1.1.1.1:80/webfsapi",
CONF_PIN: "1234",
}
@pytest.mark.parametrize(
("webfsapi_endpoint_error", "result_reason"),
[
(ConnectionError, "cannot_connect"),
(ValueError, "unknown"),
],
)
async def test_import_webfsapi_endpoint_failures(
hass: HomeAssistant, webfsapi_endpoint_error: Exception, result_reason: str
) -> None:
"""Test various failure of get_webfsapi_endpoint."""
with patch(
"afsapi.AFSAPI.get_webfsapi_endpoint",
side_effect=webfsapi_endpoint_error,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_HOST: "1.1.1.1",
CONF_PORT: 80,
CONF_PIN: "1234",
CONF_NAME: "Test name",
},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == result_reason
@pytest.mark.parametrize(
("radio_id_error", "result_reason"),
[
(ConnectionError, "cannot_connect"),
(InvalidPinException, "invalid_auth"),
(ValueError, "unknown"),
],
)
async def test_import_radio_id_failures(
hass: HomeAssistant, radio_id_error: Exception, result_reason: str
) -> None:
"""Test various failure of get_radio_id."""
with patch(
"afsapi.AFSAPI.get_radio_id",
side_effect=radio_id_error,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_HOST: "1.1.1.1",
CONF_PORT: 80,
CONF_PIN: "1234",
CONF_NAME: "Test name",
},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == result_reason
async def test_import_already_exists(
hass: HomeAssistant, config_entry: MockConfigEntry
) -> None:
"""Test import of device which already exists."""
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_HOST: "1.1.1.1",
CONF_PORT: 80,
CONF_PIN: "1234",
CONF_NAME: "Test name",
},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_form_default_pin(
hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:
"""Test manual device add with default pin."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Name of the device"
assert result2["data"] == {
CONF_WEBFSAPI_URL: "http://1.1.1.1:80/webfsapi",
CONF_PIN: "1234",
}
mock_setup_entry.assert_called_once()
async def test_form_nondefault_pin(
hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
with patch(
"afsapi.AFSAPI.get_friendly_name",
side_effect=InvalidPinException,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM
assert result2["step_id"] == "device_config"
assert result2["errors"] is None
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{CONF_PIN: "4321"},
)
await hass.async_block_till_done()
assert result3["type"] == FlowResultType.CREATE_ENTRY
assert result3["title"] == "Name of the device"
assert result3["data"] == {
"webfsapi_url": "http://1.1.1.1:80/webfsapi",
"pin": "4321",
}
mock_setup_entry.assert_called_once()
@pytest.mark.parametrize(
("friendly_name_error", "result_error"),
[
(ConnectionError, "cannot_connect"),
(InvalidPinException, "invalid_auth"),
(ValueError, "unknown"),
],
)
async def test_form_nondefault_pin_invalid(
hass: HomeAssistant, friendly_name_error: Exception, result_error: str
) -> None:
"""Test we get the proper errors when trying to validate an user-provided PIN."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
with patch(
"afsapi.AFSAPI.get_friendly_name",
side_effect=InvalidPinException,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM
assert result2["step_id"] == "device_config"
assert result2["errors"] is None
with patch(
"afsapi.AFSAPI.get_friendly_name",
side_effect=friendly_name_error,
):
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{CONF_PIN: "4321"},
)
await hass.async_block_till_done()
assert result3["type"] == FlowResultType.FORM
assert result2["step_id"] == "device_config"
assert result3["errors"] == {"base": result_error}
@pytest.mark.parametrize(
("webfsapi_endpoint_error", "result_error"),
[
(ConnectionError, "cannot_connect"),
(ValueError, "unknown"),
],
)
async def test_invalid_device_url(
hass: HomeAssistant, webfsapi_endpoint_error: Exception, result_error: str
) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
with patch(
"afsapi.AFSAPI.get_webfsapi_endpoint",
side_effect=webfsapi_endpoint_error,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM
assert result2["step_id"] == "user"
assert result2["errors"] == {"base": result_error}

View File

@@ -335,7 +335,7 @@ async def test_ingress_missing_peername(
async def test_forwarding_paths_as_requested( async def test_forwarding_paths_as_requested(
hassio_noauth_client, aioclient_mock hassio_noauth_client, aioclient_mock: AiohttpClientMocker
) -> None: ) -> None:
"""Test incomnig URLs with double encoding go out as dobule encoded.""" """Test incomnig URLs with double encoding go out as dobule encoded."""
# This double encoded string should be forwarded double-encoded too. # This double encoded string should be forwarded double-encoded too.

View File

@@ -283,7 +283,7 @@ async def test_alerts(
) )
async def test_alerts_refreshed_on_component_load( async def test_alerts_refreshed_on_component_load(
hass: HomeAssistant, hass: HomeAssistant,
hass_ws_client, hass_ws_client: WebSocketGenerator,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
ha_version, ha_version,
supervisor_info, supervisor_info,

View File

@@ -5,7 +5,7 @@ import json
from homeassistant.components.homematicip_cloud.helpers import is_error_response from homeassistant.components.homematicip_cloud.helpers import is_error_response
async def test_is_error_response(): async def test_is_error_response() -> None:
"""Test, if an response is a normal result or an error.""" """Test, if an response is a normal result or an error."""
assert not is_error_response("True") assert not is_error_response("True")
assert not is_error_response(True) assert not is_error_response(True)

View File

@@ -12,13 +12,14 @@ from homeassistant.components.lock import (
LockEntityFeature, LockEntityFeature,
) )
from homeassistant.const import ATTR_SUPPORTED_FEATURES from homeassistant.const import ATTR_SUPPORTED_FEATURES
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .helper import async_manipulate_test_data, get_and_check_entity_basics from .helper import async_manipulate_test_data, get_and_check_entity_basics
async def test_manually_configured_platform(hass): async def test_manually_configured_platform(hass: HomeAssistant) -> None:
"""Test that we do not set up an access point.""" """Test that we do not set up an access point."""
assert await async_setup_component( assert await async_setup_component(
hass, DOMAIN, {DOMAIN: {"platform": HMIPC_DOMAIN}} hass, DOMAIN, {DOMAIN: {"platform": HMIPC_DOMAIN}}
@@ -26,7 +27,9 @@ async def test_manually_configured_platform(hass):
assert not hass.data.get(HMIPC_DOMAIN) assert not hass.data.get(HMIPC_DOMAIN)
async def test_hmip_doorlockdrive(hass, default_mock_hap_factory): async def test_hmip_doorlockdrive(
hass: HomeAssistant, default_mock_hap_factory
) -> None:
"""Test HomematicipDoorLockDrive.""" """Test HomematicipDoorLockDrive."""
entity_id = "lock.haustuer" entity_id = "lock.haustuer"
entity_name = "Haustuer" entity_name = "Haustuer"
@@ -82,7 +85,9 @@ async def test_hmip_doorlockdrive(hass, default_mock_hap_factory):
assert ha_state.state == STATE_UNLOCKING assert ha_state.state == STATE_UNLOCKING
async def test_hmip_doorlockdrive_handle_errors(hass, default_mock_hap_factory): async def test_hmip_doorlockdrive_handle_errors(
hass: HomeAssistant, default_mock_hap_factory
) -> None:
"""Test HomematicipDoorLockDrive.""" """Test HomematicipDoorLockDrive."""
entity_id = "lock.haustuer" entity_id = "lock.haustuer"
entity_name = "Haustuer" entity_name = "Haustuer"

View File

@@ -1,6 +1,8 @@
"""Test the jellyfin config flow.""" """Test the jellyfin config flow."""
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest
from homeassistant import config_entries, data_entry_flow from homeassistant import config_entries, data_entry_flow
from homeassistant.components.jellyfin.const import CONF_CLIENT_DEVICE_ID, DOMAIN from homeassistant.components.jellyfin.const import CONF_CLIENT_DEVICE_ID, DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
@@ -11,6 +13,8 @@ from .const import TEST_PASSWORD, TEST_URL, TEST_USERNAME
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_abort_if_existing_entry(hass: HomeAssistant) -> None: async def test_abort_if_existing_entry(hass: HomeAssistant) -> None:
"""Check flow abort when an entry already exist.""" """Check flow abort when an entry already exist."""

View File

@@ -0,0 +1,14 @@
"""Define fixtures for kmtronic tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.kmtronic.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@@ -1,8 +1,9 @@
"""Test the kmtronic config flow.""" """Test the kmtronic config flow."""
from http import HTTPStatus from http import HTTPStatus
from unittest.mock import Mock, patch from unittest.mock import AsyncMock, Mock, patch
from aiohttp import ClientConnectorError, ClientResponseError from aiohttp import ClientConnectorError, ClientResponseError
import pytest
from homeassistant import config_entries, data_entry_flow from homeassistant import config_entries, data_entry_flow
from homeassistant.components.kmtronic.const import CONF_REVERSE, DOMAIN from homeassistant.components.kmtronic.const import CONF_REVERSE, DOMAIN
@@ -12,8 +13,10 @@ from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(hass: HomeAssistant) -> None:
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@@ -25,10 +28,7 @@ async def test_form(hass: HomeAssistant) -> None:
with patch( with patch(
"homeassistant.components.kmtronic.config_flow.KMTronicHubAPI.async_get_status", "homeassistant.components.kmtronic.config_flow.KMTronicHubAPI.async_get_status",
return_value=[Mock()], return_value=[Mock()],
), patch( ):
"homeassistant.components.kmtronic.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
@@ -37,6 +37,7 @@ async def test_form(hass: HomeAssistant) -> None:
"password": "test-password", "password": "test-password",
}, },
) )
await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == "1.1.1.1" assert result2["title"] == "1.1.1.1"
@@ -45,7 +46,6 @@ async def test_form(hass: HomeAssistant) -> None:
"username": "test-username", "username": "test-username",
"password": "test-password", "password": "test-password",
} }
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View File

@@ -47,6 +47,7 @@ async def test_formx(hass: HomeAssistant) -> None:
"password": "test-password", "password": "test-password",
}, },
) )
await hass.async_block_till_done()
mock_api_class.assert_called_once_with(ANY, "1.1.1.1") mock_api_class.assert_called_once_with(ANY, "1.1.1.1")
mock_api.__aenter__.assert_called_once() mock_api.__aenter__.assert_called_once()
@@ -60,7 +61,6 @@ async def test_formx(hass: HomeAssistant) -> None:
"host": "1.1.1.1", "host": "1.1.1.1",
"password": "test-password", "password": "test-password",
} }
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View File

@@ -60,10 +60,10 @@ async def test_flow_no_devices_found(hass: HomeAssistant) -> None:
result["flow_id"], result["flow_id"],
{}, {},
) )
await hass.async_block_till_done()
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "no_devices_found" assert result2["reason"] == "no_devices_found"
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 0 assert len(mock_setup_entry.mock_calls) == 0
@@ -87,8 +87,8 @@ async def test_flow_exceptions_caught(hass: HomeAssistant) -> None:
result["flow_id"], result["flow_id"],
{}, {},
) )
await hass.async_block_till_done()
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "no_devices_found" assert result2["reason"] == "no_devices_found"
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 0 assert len(mock_setup_entry.mock_calls) == 0

View File

@@ -0,0 +1,14 @@
"""Define fixtures for LaCrosse View tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.lacrosse_view.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@@ -1,7 +1,8 @@
"""Test the LaCrosse View config flow.""" """Test the LaCrosse View config flow."""
from unittest.mock import patch from unittest.mock import AsyncMock, patch
from lacrosse_view import Location, LoginError from lacrosse_view import Location, LoginError
import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.lacrosse_view.const import DOMAIN from homeassistant.components.lacrosse_view.const import DOMAIN
@@ -10,8 +11,10 @@ from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(hass: HomeAssistant) -> None:
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
@@ -25,8 +28,6 @@ async def test_form(hass: HomeAssistant) -> None:
), patch( ), patch(
"lacrosse_view.LaCrosse.get_locations", "lacrosse_view.LaCrosse.get_locations",
return_value=[Location(id=1, name="Test")], return_value=[Location(id=1, name="Test")],
), patch(
"homeassistant.components.lacrosse_view.async_setup_entry", return_value=True
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@@ -41,17 +42,13 @@ async def test_form(hass: HomeAssistant) -> None:
assert result2["step_id"] == "location" assert result2["step_id"] == "location"
assert result2["errors"] is None assert result2["errors"] is None
with patch( result3 = await hass.config_entries.flow.async_configure(
"homeassistant.components.lacrosse_view.async_setup_entry", result2["flow_id"],
return_value=True, {
) as mock_setup_entry: "location": "1",
result3 = await hass.config_entries.flow.async_configure( },
result2["flow_id"], )
{ await hass.async_block_till_done()
"location": "1",
},
)
await hass.async_block_till_done()
assert result3["type"] == FlowResultType.CREATE_ENTRY assert result3["type"] == FlowResultType.CREATE_ENTRY
assert result3["title"] == "Test" assert result3["title"] == "Test"
@@ -170,7 +167,9 @@ async def test_form_unexpected_error(hass: HomeAssistant) -> None:
assert result2["errors"] == {"base": "unknown"} assert result2["errors"] == {"base": "unknown"}
async def test_already_configured_device(hass: HomeAssistant) -> None: async def test_already_configured_device(
hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:
"""Test we handle invalid auth.""" """Test we handle invalid auth."""
mock_config_entry = MockConfigEntry( mock_config_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@@ -212,17 +211,13 @@ async def test_already_configured_device(hass: HomeAssistant) -> None:
assert result2["step_id"] == "location" assert result2["step_id"] == "location"
assert result2["errors"] is None assert result2["errors"] is None
with patch( result3 = await hass.config_entries.flow.async_configure(
"homeassistant.components.lacrosse_view.async_setup_entry", result2["flow_id"],
return_value=True, {
) as mock_setup_entry: "location": "1",
result3 = await hass.config_entries.flow.async_configure( },
result2["flow_id"], )
{ await hass.async_block_till_done()
"location": "1",
},
)
await hass.async_block_till_done()
assert result3["type"] == FlowResultType.ABORT assert result3["type"] == FlowResultType.ABORT
assert result3["reason"] == "already_configured" assert result3["reason"] == "already_configured"

View File

@@ -0,0 +1,15 @@
"""Define fixtures for Landis + Gyr Heat Meter tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.landisgyr_heat_meter.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry

View File

@@ -2,6 +2,7 @@
from dataclasses import dataclass from dataclasses import dataclass
from unittest.mock import patch from unittest.mock import patch
import pytest
import serial import serial
import serial.tools.list_ports import serial.tools.list_ports
@@ -14,6 +15,8 @@ from tests.common import MockConfigEntry
API_HEAT_METER_SERVICE = "homeassistant.components.landisgyr_heat_meter.config_flow.ultraheat_api.HeatMeterService" API_HEAT_METER_SERVICE = "homeassistant.components.landisgyr_heat_meter.config_flow.ultraheat_api.HeatMeterService"
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
def mock_serial_port(): def mock_serial_port():
"""Mock of a serial port.""" """Mock of a serial port."""
@@ -57,13 +60,9 @@ async def test_manual_entry(mock_heat_meter, hass: HomeAssistant) -> None:
assert result["step_id"] == "setup_serial_manual_path" assert result["step_id"] == "setup_serial_manual_path"
assert result["errors"] == {} assert result["errors"] == {}
with patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.landisgyr_heat_meter.async_setup_entry", result["flow_id"], {"device": "/dev/ttyUSB0"}
return_value=True, )
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"device": "/dev/ttyUSB0"}
)
assert result["type"] == FlowResultType.CREATE_ENTRY assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "LUGCUH50" assert result["title"] == "LUGCUH50"
@@ -122,13 +121,9 @@ async def test_manual_entry_fail(mock_heat_meter, hass: HomeAssistant) -> None:
assert result["step_id"] == "setup_serial_manual_path" assert result["step_id"] == "setup_serial_manual_path"
assert result["errors"] == {} assert result["errors"] == {}
with patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.landisgyr_heat_meter.async_setup_entry", result["flow_id"], {"device": "/dev/ttyUSB0"}
return_value=True, )
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"device": "/dev/ttyUSB0"}
)
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "setup_serial_manual_path" assert result["step_id"] == "setup_serial_manual_path"

View File

@@ -3,11 +3,13 @@ from dataclasses import dataclass
import datetime import datetime
from unittest.mock import patch from unittest.mock import patch
import serial
from homeassistant.components.homeassistant import ( from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN, DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY, SERVICE_UPDATE_ENTITY,
) )
from homeassistant.components.landisgyr_heat_meter.const import DOMAIN from homeassistant.components.landisgyr_heat_meter.const import DOMAIN, POLLING_INTERVAL
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
ATTR_LAST_RESET, ATTR_LAST_RESET,
ATTR_STATE_CLASS, ATTR_STATE_CLASS,
@@ -19,6 +21,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_ICON, ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
STATE_UNAVAILABLE,
EntityCategory, EntityCategory,
UnitOfEnergy, UnitOfEnergy,
UnitOfVolume, UnitOfVolume,
@@ -28,21 +31,29 @@ from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from tests.common import MockConfigEntry, mock_restore_cache_with_extra_data from tests.common import (
MockConfigEntry,
async_fire_time_changed,
mock_restore_cache_with_extra_data,
)
API_HEAT_METER_SERVICE = (
"homeassistant.components.landisgyr_heat_meter.ultraheat_api.HeatMeterService"
)
@dataclass @dataclass
class MockHeatMeterResponse: class MockHeatMeterResponse:
"""Mock for HeatMeterResponse.""" """Mock for HeatMeterResponse."""
heat_usage_gj: int heat_usage_gj: float
volume_usage_m3: int volume_usage_m3: float
heat_previous_year_gj: int heat_previous_year_gj: float
device_number: str device_number: str
meter_date_time: datetime.datetime meter_date_time: datetime.datetime
@patch("homeassistant.components.landisgyr_heat_meter.ultraheat_api.HeatMeterService") @patch(API_HEAT_METER_SERVICE)
async def test_create_sensors( async def test_create_sensors(
mock_heat_meter, hass: HomeAssistant, entity_registry: er.EntityRegistry mock_heat_meter, hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None: ) -> None:
@@ -57,9 +68,9 @@ async def test_create_sensors(
mock_entry.add_to_hass(hass) mock_entry.add_to_hass(hass)
mock_heat_meter_response = MockHeatMeterResponse( mock_heat_meter_response = MockHeatMeterResponse(
heat_usage_gj=123, heat_usage_gj=123.0,
volume_usage_m3=456, volume_usage_m3=456.0,
heat_previous_year_gj=111, heat_previous_year_gj=111.0,
device_number="devicenr_789", device_number="devicenr_789",
meter_date_time=dt_util.as_utc(datetime.datetime(2022, 5, 19, 19, 41, 17)), meter_date_time=dt_util.as_utc(datetime.datetime(2022, 5, 19, 19, 41, 17)),
) )
@@ -89,7 +100,7 @@ async def test_create_sensors(
state = hass.states.get("sensor.heat_meter_volume_usage") state = hass.states.get("sensor.heat_meter_volume_usage")
assert state assert state
assert state.state == "456" assert state.state == "456.0"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfVolume.CUBIC_METERS assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfVolume.CUBIC_METERS
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL
@@ -110,7 +121,7 @@ async def test_create_sensors(
assert entity_registry_entry.entity_category == EntityCategory.DIAGNOSTIC assert entity_registry_entry.entity_category == EntityCategory.DIAGNOSTIC
@patch("homeassistant.components.landisgyr_heat_meter.ultraheat_api.HeatMeterService") @patch(API_HEAT_METER_SERVICE)
async def test_restore_state(mock_heat_meter, hass: HomeAssistant) -> None: async def test_restore_state(mock_heat_meter, hass: HomeAssistant) -> None:
"""Test sensor restore state.""" """Test sensor restore state."""
# Home assistant is not running yet # Home assistant is not running yet
@@ -199,3 +210,66 @@ async def test_restore_state(mock_heat_meter, hass: HomeAssistant) -> None:
assert state assert state
assert state.state == "devicenr_789" assert state.state == "devicenr_789"
assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.attributes.get(ATTR_STATE_CLASS) is None
@patch(API_HEAT_METER_SERVICE)
async def test_exception_on_polling(mock_heat_meter, hass: HomeAssistant) -> None:
"""Test sensor."""
entry_data = {
"device": "/dev/USB0",
"model": "LUGCUH50",
"device_number": "123456789",
}
mock_entry = MockConfigEntry(domain=DOMAIN, unique_id=DOMAIN, data=entry_data)
mock_entry.add_to_hass(hass)
# First setup normally
mock_heat_meter_response = MockHeatMeterResponse(
heat_usage_gj=123.0,
volume_usage_m3=456.0,
heat_previous_year_gj=111.0,
device_number="devicenr_789",
meter_date_time=dt_util.as_utc(datetime.datetime(2022, 5, 19, 19, 41, 17)),
)
mock_heat_meter().read.return_value = mock_heat_meter_response
await hass.config_entries.async_setup(mock_entry.entry_id)
await async_setup_component(hass, HA_DOMAIN, {})
await hass.async_block_till_done()
await hass.services.async_call(
HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: "sensor.heat_meter_heat_usage"},
blocking=True,
)
await hass.async_block_till_done()
# check if initial setup succeeded
state = hass.states.get("sensor.heat_meter_heat_usage")
assert state
assert state.state == "34.16669"
# Now 'disable' the connection and wait for polling and see if it fails
mock_heat_meter().read.side_effect = serial.serialutil.SerialException
async_fire_time_changed(hass, dt_util.utcnow() + POLLING_INTERVAL)
await hass.async_block_till_done()
state = hass.states.get("sensor.heat_meter_heat_usage")
assert state.state == STATE_UNAVAILABLE
# Now 'enable' and see if next poll succeeds
mock_heat_meter_response = MockHeatMeterResponse(
heat_usage_gj=124.0,
volume_usage_m3=457.0,
heat_previous_year_gj=112.0,
device_number="devicenr_789",
meter_date_time=dt_util.as_utc(datetime.datetime(2022, 5, 19, 20, 41, 17)),
)
mock_heat_meter().read.return_value = mock_heat_meter_response
mock_heat_meter().read.side_effect = None
async_fire_time_changed(hass, dt_util.utcnow() + POLLING_INTERVAL)
await hass.async_block_till_done()
state = hass.states.get("sensor.heat_meter_heat_usage")
assert state
assert state.state == "34.44447"

View File

@@ -67,13 +67,13 @@ async def test_bridge_import_flow(hass: HomeAssistant) -> None:
context={"source": config_entries.SOURCE_IMPORT}, context={"source": config_entries.SOURCE_IMPORT},
data=entry_mock_data, data=entry_mock_data,
) )
await hass.async_block_till_done()
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == CasetaConfigFlow.ENTRY_DEFAULT_TITLE assert result["title"] == CasetaConfigFlow.ENTRY_DEFAULT_TITLE
assert result["data"] == entry_mock_data assert result["data"] == entry_mock_data
assert result["result"].unique_id == "000004d2" assert result["result"].unique_id == "000004d2"
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View File

@@ -381,6 +381,8 @@ async def test_import_discovery_integration(
type=type_in_discovery, type=type_in_discovery,
), ),
) )
await hass.async_block_till_done()
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == TEST_NAME assert result["title"] == TEST_NAME
assert result["data"] == { assert result["data"] == {
@@ -395,7 +397,6 @@ async def test_import_discovery_integration(
mock_save_json.assert_called_once() mock_save_json.assert_called_once()
mock_remove.assert_not_called() mock_remove.assert_not_called()
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
@@ -431,6 +432,7 @@ async def test_ssdp_discovery(hass: HomeAssistant) -> None:
assert result["step_id"] == "link" assert result["step_id"] == "link"
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == TEST_NAME assert result2["title"] == TEST_NAME
@@ -439,5 +441,4 @@ async def test_ssdp_discovery(hass: HomeAssistant) -> None:
CONF_TOKEN: TEST_TOKEN, CONF_TOKEN: TEST_TOKEN,
} }
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View File

@@ -108,7 +108,8 @@ async def test_form_already_configured(
result["flow_id"], result["flow_id"],
{"api_key": "test"}, {"api_key": "test"},
) )
await hass.async_block_till_done()
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "already_configured" assert result2["reason"] == "already_configured"
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 0 assert len(mock_setup_entry.mock_calls) == 0

View File

@@ -76,7 +76,8 @@ async def test_user_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> No
CONF_HOST: "1.2.3.4", CONF_HOST: "1.2.3.4",
CONF_PORT: 1234, CONF_PORT: 1234,
} }
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
@@ -102,8 +103,6 @@ async def test_user_duplicate(
) )
assert result["type"] == FlowResultType.ABORT assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.usefixtures("filled_device_registry") @pytest.mark.usefixtures("filled_device_registry")

View File

@@ -1,5 +1,4 @@
"""Test the pjlink media player platform.""" """Test the pjlink media player platform."""
from datetime import timedelta from datetime import timedelta
import socket import socket
from unittest.mock import create_autospec, patch from unittest.mock import create_autospec, patch
@@ -11,6 +10,7 @@ import pytest
import homeassistant.components.media_player as media_player import homeassistant.components.media_player as media_player
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util import dt from homeassistant.util import dt
@@ -48,7 +48,9 @@ def mocked_projector(projector_from_address):
@pytest.mark.parametrize("side_effect", [socket.timeout, OSError]) @pytest.mark.parametrize("side_effect", [socket.timeout, OSError])
async def test_offline_initialization(projector_from_address, hass, side_effect): async def test_offline_initialization(
projector_from_address, hass: HomeAssistant, side_effect
) -> None:
"""Test initialization of a device that is offline.""" """Test initialization of a device that is offline."""
with assert_setup_component(1, media_player.DOMAIN): with assert_setup_component(1, media_player.DOMAIN):
@@ -71,7 +73,7 @@ async def test_offline_initialization(projector_from_address, hass, side_effect)
assert state.state == "unavailable" assert state.state == "unavailable"
async def test_initialization(projector_from_address, hass): async def test_initialization(projector_from_address, hass: HomeAssistant) -> None:
"""Test a device that is available.""" """Test a device that is available."""
with assert_setup_component(1, media_player.DOMAIN): with assert_setup_component(1, media_player.DOMAIN):
@@ -108,7 +110,9 @@ async def test_initialization(projector_from_address, hass):
@pytest.mark.parametrize("power_state", ["on", "warm-up"]) @pytest.mark.parametrize("power_state", ["on", "warm-up"])
async def test_on_state_init(projector_from_address, hass, power_state): async def test_on_state_init(
projector_from_address, hass: HomeAssistant, power_state
) -> None:
"""Test a device that is available.""" """Test a device that is available."""
with assert_setup_component(1, media_player.DOMAIN): with assert_setup_component(1, media_player.DOMAIN):
@@ -139,7 +143,7 @@ async def test_on_state_init(projector_from_address, hass, power_state):
assert state.attributes["source"] == "HDMI 1" assert state.attributes["source"] == "HDMI 1"
async def test_api_error(projector_from_address, hass): async def test_api_error(projector_from_address, hass: HomeAssistant) -> None:
"""Test invalid api responses.""" """Test invalid api responses."""
with assert_setup_component(1, media_player.DOMAIN): with assert_setup_component(1, media_player.DOMAIN):
@@ -171,7 +175,7 @@ async def test_api_error(projector_from_address, hass):
assert state.state == "off" assert state.state == "off"
async def test_update_unavailable(projector_from_address, hass): async def test_update_unavailable(projector_from_address, hass: HomeAssistant) -> None:
"""Test update to a device that is unavailable.""" """Test update to a device that is unavailable."""
with assert_setup_component(1, media_player.DOMAIN): with assert_setup_component(1, media_player.DOMAIN):
@@ -209,7 +213,7 @@ async def test_update_unavailable(projector_from_address, hass):
assert state.state == "unavailable" assert state.state == "unavailable"
async def test_unavailable_time(mocked_projector, hass): async def test_unavailable_time(mocked_projector, hass: HomeAssistant) -> None:
"""Test unavailable time projector error.""" """Test unavailable time projector error."""
assert await async_setup_component( assert await async_setup_component(
@@ -240,7 +244,7 @@ async def test_unavailable_time(mocked_projector, hass):
assert "is_volume_muted" not in state.attributes assert "is_volume_muted" not in state.attributes
async def test_turn_off(mocked_projector, hass): async def test_turn_off(mocked_projector, hass: HomeAssistant) -> None:
"""Test turning off beamer.""" """Test turning off beamer."""
assert await async_setup_component( assert await async_setup_component(
@@ -265,7 +269,7 @@ async def test_turn_off(mocked_projector, hass):
mocked_projector.set_power.assert_called_with("off") mocked_projector.set_power.assert_called_with("off")
async def test_turn_on(mocked_projector, hass): async def test_turn_on(mocked_projector, hass: HomeAssistant) -> None:
"""Test turning on beamer.""" """Test turning on beamer."""
assert await async_setup_component( assert await async_setup_component(
@@ -290,7 +294,7 @@ async def test_turn_on(mocked_projector, hass):
mocked_projector.set_power.assert_called_with("on") mocked_projector.set_power.assert_called_with("on")
async def test_mute(mocked_projector, hass): async def test_mute(mocked_projector, hass: HomeAssistant) -> None:
"""Test muting beamer.""" """Test muting beamer."""
assert await async_setup_component( assert await async_setup_component(
@@ -315,7 +319,7 @@ async def test_mute(mocked_projector, hass):
mocked_projector.set_mute.assert_called_with(MUTE_AUDIO, True) mocked_projector.set_mute.assert_called_with(MUTE_AUDIO, True)
async def test_unmute(mocked_projector, hass): async def test_unmute(mocked_projector, hass: HomeAssistant) -> None:
"""Test unmuting beamer.""" """Test unmuting beamer."""
assert await async_setup_component( assert await async_setup_component(
@@ -340,7 +344,7 @@ async def test_unmute(mocked_projector, hass):
mocked_projector.set_mute.assert_called_with(MUTE_AUDIO, False) mocked_projector.set_mute.assert_called_with(MUTE_AUDIO, False)
async def test_select_source(mocked_projector, hass): async def test_select_source(mocked_projector, hass: HomeAssistant) -> None:
"""Test selecting source.""" """Test selecting source."""
assert await async_setup_component( assert await async_setup_component(

View File

@@ -9,10 +9,11 @@ from homeassistant.components import camera
from homeassistant.components.camera import Image from homeassistant.components.camera import Image
from homeassistant.components.prosegur.const import DOMAIN from homeassistant.components.prosegur.const import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
async def test_camera(hass, init_integration): async def test_camera(hass: HomeAssistant, init_integration) -> None:
"""Test prosegur get_image.""" """Test prosegur get_image."""
image = await camera.async_get_image(hass, "camera.test_cam") image = await camera.async_get_image(hass, "camera.test_cam")
@@ -20,7 +21,12 @@ async def test_camera(hass, init_integration):
assert image == Image(content_type="image/jpeg", content=b"ABC") assert image == Image(content_type="image/jpeg", content=b"ABC")
async def test_camera_fail(hass, init_integration, mock_install, caplog): async def test_camera_fail(
hass: HomeAssistant,
init_integration,
mock_install,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test prosegur get_image fails.""" """Test prosegur get_image fails."""
mock_install.get_image = AsyncMock( mock_install.get_image = AsyncMock(
@@ -37,7 +43,9 @@ async def test_camera_fail(hass, init_integration, mock_install, caplog):
assert "Image test_cam doesn't exist" in caplog.text assert "Image test_cam doesn't exist" in caplog.text
async def test_request_image(hass, init_integration, mock_install): async def test_request_image(
hass: HomeAssistant, init_integration, mock_install
) -> None:
"""Test the camera request image service.""" """Test the camera request image service."""
await hass.services.async_call( await hass.services.async_call(
@@ -50,7 +58,12 @@ async def test_request_image(hass, init_integration, mock_install):
assert mock_install.request_image.called assert mock_install.request_image.called
async def test_request_image_fail(hass, init_integration, mock_install, caplog): async def test_request_image_fail(
hass: HomeAssistant,
init_integration,
mock_install,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the camera request image service fails.""" """Test the camera request image service fails."""
mock_install.request_image = AsyncMock(side_effect=ProsegurException()) mock_install.request_image = AsyncMock(side_effect=ProsegurException())

View File

@@ -1,11 +1,18 @@
"""Test Prosegur diagnostics.""" """Test Prosegur diagnostics."""
from unittest.mock import patch from unittest.mock import patch
from homeassistant.core import HomeAssistant
from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
async def test_diagnostics(hass, hass_client, init_integration, mock_install): async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
init_integration,
mock_install,
) -> None:
"""Test generating diagnostics for a config entry.""" """Test generating diagnostics for a config entry."""
with patch( with patch(

View File

@@ -1805,7 +1805,7 @@ def record_states(hass):
return zero, four, states return zero, four, states
def test_cache_key_for_generate_statistics_during_period_stmt(): def test_cache_key_for_generate_statistics_during_period_stmt() -> None:
"""Test cache key for _generate_statistics_during_period_stmt.""" """Test cache key for _generate_statistics_during_period_stmt."""
columns = select(StatisticsShortTerm.metadata_id, StatisticsShortTerm.start_ts) columns = select(StatisticsShortTerm.metadata_id, StatisticsShortTerm.start_ts)
stmt = _generate_statistics_during_period_stmt( stmt = _generate_statistics_during_period_stmt(
@@ -1835,7 +1835,7 @@ def test_cache_key_for_generate_statistics_during_period_stmt():
assert cache_key_1 != cache_key_3 assert cache_key_1 != cache_key_3
def test_cache_key_for_generate_get_metadata_stmt(): def test_cache_key_for_generate_get_metadata_stmt() -> None:
"""Test cache key for _generate_get_metadata_stmt.""" """Test cache key for _generate_get_metadata_stmt."""
stmt_mean = _generate_get_metadata_stmt([0], "mean") stmt_mean = _generate_get_metadata_stmt([0], "mean")
stmt_mean2 = _generate_get_metadata_stmt([1], "mean") stmt_mean2 = _generate_get_metadata_stmt([1], "mean")
@@ -1846,7 +1846,7 @@ def test_cache_key_for_generate_get_metadata_stmt():
assert stmt_mean._generate_cache_key() != stmt_none._generate_cache_key() assert stmt_mean._generate_cache_key() != stmt_none._generate_cache_key()
def test_cache_key_for_generate_max_mean_min_statistic_in_sub_period_stmt(): def test_cache_key_for_generate_max_mean_min_statistic_in_sub_period_stmt() -> None:
"""Test cache key for _generate_max_mean_min_statistic_in_sub_period_stmt.""" """Test cache key for _generate_max_mean_min_statistic_in_sub_period_stmt."""
columns = select(StatisticsShortTerm.metadata_id, StatisticsShortTerm.start_ts) columns = select(StatisticsShortTerm.metadata_id, StatisticsShortTerm.start_ts)
stmt = _generate_max_mean_min_statistic_in_sub_period_stmt( stmt = _generate_max_mean_min_statistic_in_sub_period_stmt(
@@ -1883,7 +1883,7 @@ def test_cache_key_for_generate_max_mean_min_statistic_in_sub_period_stmt():
assert cache_key_1 != cache_key_3 assert cache_key_1 != cache_key_3
def test_cache_key_for_generate_statistics_at_time_stmt(): def test_cache_key_for_generate_statistics_at_time_stmt() -> None:
"""Test cache key for _generate_statistics_at_time_stmt.""" """Test cache key for _generate_statistics_at_time_stmt."""
columns = select(StatisticsShortTerm.metadata_id, StatisticsShortTerm.start_ts) columns = select(StatisticsShortTerm.metadata_id, StatisticsShortTerm.start_ts)
stmt = _generate_statistics_at_time_stmt(columns, StatisticsShortTerm, {0}, 0.0) stmt = _generate_statistics_at_time_stmt(columns, StatisticsShortTerm, {0}, 0.0)

View File

@@ -4,7 +4,7 @@ import json
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import pytest import pytest
from sfrbox_api.models import DslInfo, SystemInfo from sfrbox_api.models import DslInfo, FtthInfo, SystemInfo, WanInfo
from homeassistant.components.sfr_box.const import DOMAIN from homeassistant.components.sfr_box.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER, ConfigEntry from homeassistant.config_entries import SOURCE_USER, ConfigEntry
@@ -57,17 +57,6 @@ def get_config_entry_with_auth(hass: HomeAssistant) -> ConfigEntry:
return config_entry_with_auth return config_entry_with_auth
@pytest.fixture
def system_get_info() -> Generator[SystemInfo, None, None]:
"""Fixture for SFRBox.system_get_info."""
system_info = SystemInfo(**json.loads(load_fixture("system_getInfo.json", DOMAIN)))
with patch(
"homeassistant.components.sfr_box.coordinator.SFRBox.system_get_info",
return_value=system_info,
):
yield system_info
@pytest.fixture @pytest.fixture
def dsl_get_info() -> Generator[DslInfo, None, None]: def dsl_get_info() -> Generator[DslInfo, None, None]:
"""Fixture for SFRBox.dsl_get_info.""" """Fixture for SFRBox.dsl_get_info."""
@@ -77,3 +66,36 @@ def dsl_get_info() -> Generator[DslInfo, None, None]:
return_value=dsl_info, return_value=dsl_info,
): ):
yield dsl_info yield dsl_info
@pytest.fixture
def ftth_get_info() -> Generator[FtthInfo, None, None]:
"""Fixture for SFRBox.ftth_get_info."""
info = FtthInfo(**json.loads(load_fixture("ftth_getInfo.json", DOMAIN)))
with patch(
"homeassistant.components.sfr_box.coordinator.SFRBox.ftth_get_info",
return_value=info,
):
yield info
@pytest.fixture
def system_get_info() -> Generator[SystemInfo, None, None]:
"""Fixture for SFRBox.system_get_info."""
info = SystemInfo(**json.loads(load_fixture("system_getInfo.json", DOMAIN)))
with patch(
"homeassistant.components.sfr_box.coordinator.SFRBox.system_get_info",
return_value=info,
):
yield info
@pytest.fixture
def wan_get_info() -> Generator[WanInfo, None, None]:
"""Fixture for SFRBox.wan_get_info."""
info = WanInfo(**json.loads(load_fixture("wan_getInfo.json", DOMAIN)))
with patch(
"homeassistant.components.sfr_box.coordinator.SFRBox.wan_get_info",
return_value=info,
):
yield info

View File

@@ -0,0 +1,4 @@
{
"status": "down",
"wanfibre": "out"
}

View File

@@ -0,0 +1,11 @@
{
"status": "up",
"uptime": 297464,
"ip_addr": "1.2.3.4",
"infra": "adsl",
"mode": "adsl/routed",
"infra6": "",
"status6": "down",
"uptime6": null,
"ipv6_addr": ""
}

View File

@@ -0,0 +1,60 @@
# serializer version: 1
# name: test_entry_diagnostics
dict({
'data': dict({
'dsl': dict({
'attenuation_down': 28.5,
'attenuation_up': 20.8,
'counter': 16,
'crc': 0,
'line_status': 'No Defect',
'linemode': 'ADSL2+',
'noise_down': 5.8,
'noise_up': 6.0,
'rate_down': 5549,
'rate_up': 187,
'status': 'up',
'training': 'Showtime',
'uptime': 450796,
}),
'ftth': dict({
'status': 'down',
'wanfibre': 'out',
}),
'system': dict({
'alimvoltage': 12251,
'current_datetime': '202212282233',
'idur': 'RP3P85K',
'mac_addr': '**REDACTED**',
'net_infra': 'adsl',
'net_mode': 'router',
'product_id': 'NB6VAC-FXC-r0',
'refclient': '',
'serial_number': '**REDACTED**',
'temperature': 27560,
'uptime': 2353575,
'version_bootloader': 'NB6VAC-BOOTLOADER-R4.0.8',
'version_dsldriver': 'NB6VAC-XDSL-A2pv6F039p',
'version_mainfirmware': 'NB6VAC-MAIN-R4.0.44k',
'version_rescuefirmware': 'NB6VAC-MAIN-R4.0.44k',
}),
'wan': dict({
'infra': 'adsl',
'infra6': '',
'ip_addr': '1.2.3.4',
'ipv6_addr': '',
'mode': 'adsl/routed',
'status': 'up',
'status6': 'down',
'uptime': 297464,
'uptime6': None,
}),
}),
'entry': dict({
'data': dict({
'host': '192.168.0.1',
}),
'title': 'Mock Title',
}),
})
# ---

View File

@@ -3,15 +3,17 @@ from collections.abc import Generator
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.diagnostics import REDACTED
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
pytestmark = pytest.mark.usefixtures("system_get_info", "dsl_get_info") pytestmark = pytest.mark.usefixtures(
"dsl_get_info", "ftth_get_info", "system_get_info", "wan_get_info"
)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@@ -22,49 +24,16 @@ def override_platforms() -> Generator[None, None, None]:
async def test_entry_diagnostics( async def test_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry, hass_client: ClientSessionGenerator hass: HomeAssistant,
config_entry: ConfigEntry,
hass_client: ClientSessionGenerator,
snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test config entry diagnostics.""" """Test config entry diagnostics."""
await hass.config_entries.async_setup(config_entry.entry_id) await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { assert (
"entry": { await get_diagnostics_for_config_entry(hass, hass_client, config_entry)
"data": {"host": "192.168.0.1"}, == snapshot
"title": "Mock Title", )
},
"data": {
"dsl": {
"attenuation_down": 28.5,
"attenuation_up": 20.8,
"counter": 16,
"crc": 0,
"line_status": "No Defect",
"linemode": "ADSL2+",
"noise_down": 5.8,
"noise_up": 6.0,
"rate_down": 5549,
"rate_up": 187,
"status": "up",
"training": "Showtime",
"uptime": 450796,
},
"system": {
"alimvoltage": 12251,
"current_datetime": "202212282233",
"idur": "RP3P85K",
"mac_addr": REDACTED,
"net_infra": "adsl",
"net_mode": "router",
"product_id": "NB6VAC-FXC-r0",
"refclient": "",
"serial_number": REDACTED,
"temperature": 27560,
"uptime": 2353575,
"version_bootloader": "NB6VAC-BOOTLOADER-R4.0.8",
"version_dsldriver": "NB6VAC-XDSL-A2pv6F039p",
"version_mainfirmware": "NB6VAC-MAIN-R4.0.44k",
"version_rescuefirmware": "NB6VAC-MAIN-R4.0.44k",
},
},
}

View File

@@ -119,10 +119,10 @@ async def test_success(
context={"source": config_entries.SOURCE_USER}, context={"source": config_entries.SOURCE_USER},
data=FIXTURE_USER_INPUT, data=FIXTURE_USER_INPUT,
) )
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"][CONF_URL] == FIXTURE_USER_INPUT[CONF_URL] assert result["data"][CONF_URL] == FIXTURE_USER_INPUT[CONF_URL]
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1

View File

@@ -225,7 +225,7 @@ async def test_template_position(hass: HomeAssistant, start_ha) -> None:
}, },
], ],
) )
async def test_template_not_optimistic(hass, start_ha): async def test_template_not_optimistic(hass: HomeAssistant, start_ha) -> None:
"""Test the is_closed attribute.""" """Test the is_closed attribute."""
state = hass.states.get("cover.test_template_cover") state = hass.states.get("cover.test_template_cover")
assert state.state == STATE_UNKNOWN assert state.state == STATE_UNKNOWN

View File

@@ -185,3 +185,109 @@ ROUTER_DISCOVERY_HASS_MISSING_MANDATORY_DATA = {
}, },
"interface_index": None, "interface_index": None,
} }
ROUTER_DISCOVERY_HASS_NO_ACTIVE_TIMESTAMP = {
"type_": "_meshcop._udp.local.",
"name": "HomeAssistant OpenThreadBorderRouter #0BBF._meshcop._udp.local.",
"addresses": [b"\xc0\xa8\x00s"],
"port": 49153,
"weight": 0,
"priority": 0,
"server": "core-silabs-multiprotocol.local.",
"properties": {
b"rv": b"1",
b"vn": b"HomeAssistant",
b"mn": b"OpenThreadBorderRouter",
b"nn": b"OpenThread HC",
b"xp": b"\xe6\x0f\xc7\xc1\x86!,\xe5",
b"tv": b"1.3.0",
b"xa": b"\xae\xeb/YKW\x0b\xbf",
b"sb": b"\x00\x00\x01\xb1",
b"pt": b"\x8f\x06Q~",
b"sq": b"3",
b"bb": b"\xf0\xbf",
b"dn": b"DefaultDomain",
},
"interface_index": None,
}
ROUTER_DISCOVERY_HASS_NO_STATE_BITMAP = {
"type_": "_meshcop._udp.local.",
"name": "HomeAssistant OpenThreadBorderRouter #0BBF._meshcop._udp.local.",
"addresses": [b"\xc0\xa8\x00s"],
"port": 49153,
"weight": 0,
"priority": 0,
"server": "core-silabs-multiprotocol.local.",
"properties": {
b"rv": b"1",
b"vn": b"HomeAssistant",
b"mn": b"OpenThreadBorderRouter",
b"nn": b"OpenThread HC",
b"xp": b"\xe6\x0f\xc7\xc1\x86!,\xe5",
b"tv": b"1.3.0",
b"xa": b"\xae\xeb/YKW\x0b\xbf",
b"at": b"\x00\x00\x00\x00\x00\x01\x00\x00",
b"pt": b"\x8f\x06Q~",
b"sq": b"3",
b"bb": b"\xf0\xbf",
b"dn": b"DefaultDomain",
},
"interface_index": None,
}
ROUTER_DISCOVERY_HASS_BAD_STATE_BITMAP = {
"type_": "_meshcop._udp.local.",
"name": "HomeAssistant OpenThreadBorderRouter #0BBF._meshcop._udp.local.",
"addresses": [b"\xc0\xa8\x00s"],
"port": 49153,
"weight": 0,
"priority": 0,
"server": "core-silabs-multiprotocol.local.",
"properties": {
b"rv": b"1",
b"vn": b"HomeAssistant",
b"mn": b"OpenThreadBorderRouter",
b"nn": b"OpenThread HC",
b"xp": b"\xe6\x0f\xc7\xc1\x86!,\xe5",
b"tv": b"1.3.0",
b"xa": b"\xae\xeb/YKW\x0b\xbf",
b"sb": b"\xff\x00\x01\xb1",
b"at": b"\x00\x00\x00\x00\x00\x01\x00\x00",
b"pt": b"\x8f\x06Q~",
b"sq": b"3",
b"bb": b"\xf0\xbf",
b"dn": b"DefaultDomain",
},
"interface_index": None,
}
ROUTER_DISCOVERY_HASS_STATE_BITMAP_NOT_ACTIVE = {
"type_": "_meshcop._udp.local.",
"name": "HomeAssistant OpenThreadBorderRouter #0BBF._meshcop._udp.local.",
"addresses": [b"\xc0\xa8\x00s"],
"port": 49153,
"weight": 0,
"priority": 0,
"server": "core-silabs-multiprotocol.local.",
"properties": {
b"rv": b"1",
b"vn": b"HomeAssistant",
b"mn": b"OpenThreadBorderRouter",
b"nn": b"OpenThread HC",
b"xp": b"\xe6\x0f\xc7\xc1\x86!,\xe5",
b"tv": b"1.3.0",
b"xa": b"\xae\xeb/YKW\x0b\xbf",
b"sb": b"\x00\x00\x01\x31",
b"at": b"\x00\x00\x00\x00\x00\x01\x00\x00",
b"pt": b"\x8f\x06Q~",
b"sq": b"3",
b"bb": b"\xf0\xbf",
b"dn": b"DefaultDomain",
},
"interface_index": None,
}

View File

@@ -14,8 +14,12 @@ from . import (
ROUTER_DISCOVERY_GOOGLE_1, ROUTER_DISCOVERY_GOOGLE_1,
ROUTER_DISCOVERY_HASS, ROUTER_DISCOVERY_HASS,
ROUTER_DISCOVERY_HASS_BAD_DATA, ROUTER_DISCOVERY_HASS_BAD_DATA,
ROUTER_DISCOVERY_HASS_BAD_STATE_BITMAP,
ROUTER_DISCOVERY_HASS_MISSING_DATA, ROUTER_DISCOVERY_HASS_MISSING_DATA,
ROUTER_DISCOVERY_HASS_MISSING_MANDATORY_DATA, ROUTER_DISCOVERY_HASS_MISSING_MANDATORY_DATA,
ROUTER_DISCOVERY_HASS_NO_ACTIVE_TIMESTAMP,
ROUTER_DISCOVERY_HASS_NO_STATE_BITMAP,
ROUTER_DISCOVERY_HASS_STATE_BITMAP_NOT_ACTIVE,
) )
@@ -67,14 +71,15 @@ async def test_discover_routers(hass: HomeAssistant, mock_async_zeroconf: None)
assert discovered[-1] == ( assert discovered[-1] == (
"aeeb2f594b570bbf", "aeeb2f594b570bbf",
discovery.ThreadRouterDiscoveryData( discovery.ThreadRouterDiscoveryData(
addresses=["192.168.0.115"],
brand="homeassistant", brand="homeassistant",
extended_pan_id="e60fc7c186212ce5", extended_pan_id="e60fc7c186212ce5",
model_name="OpenThreadBorderRouter", model_name="OpenThreadBorderRouter",
network_name="OpenThread HC", network_name="OpenThread HC",
server="core-silabs-multiprotocol.local.", server="core-silabs-multiprotocol.local.",
vendor_name="HomeAssistant",
thread_version="1.3.0", thread_version="1.3.0",
addresses=["192.168.0.115"], unconfigured=None,
vendor_name="HomeAssistant",
), ),
) )
@@ -91,14 +96,15 @@ async def test_discover_routers(hass: HomeAssistant, mock_async_zeroconf: None)
assert discovered[-1] == ( assert discovered[-1] == (
"f6a99b425a67abed", "f6a99b425a67abed",
discovery.ThreadRouterDiscoveryData( discovery.ThreadRouterDiscoveryData(
addresses=["192.168.0.124"],
brand="google", brand="google",
extended_pan_id="9e75e256f61409a3", extended_pan_id="9e75e256f61409a3",
model_name="Google Nest Hub", model_name="Google Nest Hub",
network_name="NEST-PAN-E1AF", network_name="NEST-PAN-E1AF",
server="2d99f293-cd8e-2770-8dd2-6675de9fa000.local.", server="2d99f293-cd8e-2770-8dd2-6675de9fa000.local.",
vendor_name="Google Inc.",
thread_version="1.3.0", thread_version="1.3.0",
addresses=["192.168.0.124"], unconfigured=None,
vendor_name="Google Inc.",
), ),
) )
@@ -130,6 +136,56 @@ async def test_discover_routers(hass: HomeAssistant, mock_async_zeroconf: None)
mock_async_zeroconf.async_remove_service_listener.assert_called_once_with(listener) mock_async_zeroconf.async_remove_service_listener.assert_called_once_with(listener)
@pytest.mark.parametrize(
("data", "unconfigured"),
[
(ROUTER_DISCOVERY_HASS_NO_ACTIVE_TIMESTAMP, True),
(ROUTER_DISCOVERY_HASS_BAD_STATE_BITMAP, None),
(ROUTER_DISCOVERY_HASS_NO_STATE_BITMAP, None),
(ROUTER_DISCOVERY_HASS_STATE_BITMAP_NOT_ACTIVE, True),
],
)
async def test_discover_routers_unconfigured(
hass: HomeAssistant, mock_async_zeroconf: None, data, unconfigured
) -> None:
"""Test discovering thread routers with bad or missing vendor mDNS data."""
mock_async_zeroconf.async_add_service_listener = AsyncMock()
mock_async_zeroconf.async_remove_service_listener = AsyncMock()
mock_async_zeroconf.async_get_service_info = AsyncMock()
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
# Start Thread router discovery
router_discovered_removed = Mock()
thread_disovery = discovery.ThreadRouterDiscovery(
hass, router_discovered_removed, router_discovered_removed
)
await thread_disovery.async_start()
listener: discovery.ThreadRouterDiscovery.ThreadServiceListener = (
mock_async_zeroconf.async_add_service_listener.mock_calls[0][1][1]
)
# Discover a service with bad or missing data
mock_async_zeroconf.async_get_service_info.return_value = AsyncServiceInfo(**data)
listener.add_service(None, data["type_"], data["name"])
await hass.async_block_till_done()
router_discovered_removed.assert_called_once_with(
"aeeb2f594b570bbf",
discovery.ThreadRouterDiscoveryData(
addresses=["192.168.0.115"],
brand="homeassistant",
extended_pan_id="e60fc7c186212ce5",
model_name="OpenThreadBorderRouter",
network_name="OpenThread HC",
server="core-silabs-multiprotocol.local.",
thread_version="1.3.0",
unconfigured=unconfigured,
vendor_name="HomeAssistant",
),
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"data", (ROUTER_DISCOVERY_HASS_BAD_DATA, ROUTER_DISCOVERY_HASS_MISSING_DATA) "data", (ROUTER_DISCOVERY_HASS_BAD_DATA, ROUTER_DISCOVERY_HASS_MISSING_DATA)
) )
@@ -161,14 +217,15 @@ async def test_discover_routers_bad_data(
router_discovered_removed.assert_called_once_with( router_discovered_removed.assert_called_once_with(
"aeeb2f594b570bbf", "aeeb2f594b570bbf",
discovery.ThreadRouterDiscoveryData( discovery.ThreadRouterDiscoveryData(
addresses=["192.168.0.115"],
brand=None, brand=None,
extended_pan_id="e60fc7c186212ce5", extended_pan_id="e60fc7c186212ce5",
model_name="OpenThreadBorderRouter", model_name="OpenThreadBorderRouter",
network_name="OpenThread HC", network_name="OpenThread HC",
server="core-silabs-multiprotocol.local.", server="core-silabs-multiprotocol.local.",
vendor_name=None,
thread_version="1.3.0", thread_version="1.3.0",
addresses=["192.168.0.115"], unconfigured=None,
vendor_name=None,
), ),
) )

View File

@@ -234,14 +234,15 @@ async def test_discover_routers(
assert msg == { assert msg == {
"event": { "event": {
"data": { "data": {
"addresses": ["192.168.0.115"],
"brand": "homeassistant", "brand": "homeassistant",
"extended_pan_id": "e60fc7c186212ce5", "extended_pan_id": "e60fc7c186212ce5",
"model_name": "OpenThreadBorderRouter", "model_name": "OpenThreadBorderRouter",
"network_name": "OpenThread HC", "network_name": "OpenThread HC",
"server": "core-silabs-multiprotocol.local.", "server": "core-silabs-multiprotocol.local.",
"vendor_name": "HomeAssistant",
"addresses": ["192.168.0.115"],
"thread_version": "1.3.0", "thread_version": "1.3.0",
"unconfigured": None,
"vendor_name": "HomeAssistant",
}, },
"key": "aeeb2f594b570bbf", "key": "aeeb2f594b570bbf",
"type": "router_discovered", "type": "router_discovered",
@@ -261,14 +262,15 @@ async def test_discover_routers(
assert msg == { assert msg == {
"event": { "event": {
"data": { "data": {
"addresses": ["192.168.0.124"],
"brand": "google", "brand": "google",
"extended_pan_id": "9e75e256f61409a3", "extended_pan_id": "9e75e256f61409a3",
"model_name": "Google Nest Hub", "model_name": "Google Nest Hub",
"network_name": "NEST-PAN-E1AF", "network_name": "NEST-PAN-E1AF",
"server": "2d99f293-cd8e-2770-8dd2-6675de9fa000.local.", "server": "2d99f293-cd8e-2770-8dd2-6675de9fa000.local.",
"vendor_name": "Google Inc.",
"thread_version": "1.3.0", "thread_version": "1.3.0",
"addresses": ["192.168.0.124"], "unconfigured": None,
"vendor_name": "Google Inc.",
}, },
"key": "f6a99b425a67abed", "key": "f6a99b425a67abed",
"type": "router_discovered", "type": "router_discovered",

View File

@@ -70,7 +70,9 @@ async def test_create_entry(recorder_mock: Recorder, hass: HomeAssistant) -> Non
(FatalHttpException(404), ERR_CLIENT), (FatalHttpException(404), ERR_CLIENT),
], ],
) )
async def test_create_entry_exceptions(recorder_mock, hass, exception, expected_error): async def test_create_entry_exceptions(
recorder_mock: Recorder, hass: HomeAssistant, exception, expected_error
) -> None:
"""Test create entry from user input.""" """Test create entry from user input."""
test_data = { test_data = {
CONF_ACCESS_TOKEN: "valid", CONF_ACCESS_TOKEN: "valid",
@@ -93,7 +95,9 @@ async def test_create_entry_exceptions(recorder_mock, hass, exception, expected_
assert result["errors"][CONF_ACCESS_TOKEN] == expected_error assert result["errors"][CONF_ACCESS_TOKEN] == expected_error
async def test_flow_entry_already_exists(recorder_mock, hass, config_entry): async def test_flow_entry_already_exists(
recorder_mock: Recorder, hass: HomeAssistant, config_entry
) -> None:
"""Test user input for config_entry that already exists.""" """Test user input for config_entry that already exists."""
test_data = { test_data = {
CONF_ACCESS_TOKEN: "valid", CONF_ACCESS_TOKEN: "valid",

View File

@@ -91,7 +91,9 @@ async def test_calendar_entity_unique_id(
@patch("homeassistant.components.todoist.calendar.TodoistAPIAsync") @patch("homeassistant.components.todoist.calendar.TodoistAPIAsync")
async def test_update_entity_for_custom_project_with_labels_on(todoist_api, hass, api): async def test_update_entity_for_custom_project_with_labels_on(
todoist_api, hass: HomeAssistant, api
) -> None:
"""Test that the calendar's state is on for a custom project using labels.""" """Test that the calendar's state is on for a custom project using labels."""
todoist_api.return_value = api todoist_api.return_value = api
assert await setup.async_setup_component( assert await setup.async_setup_component(

View File

@@ -1,5 +1,4 @@
"""Tests for the Twente Milieu sensors.""" """Tests for the Twente Milieu sensors."""
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion

View File

@@ -1103,7 +1103,7 @@ async def test_state_template(hass: HomeAssistant) -> None:
assert hass.states.get("media_player.tv").state == STATE_OFF assert hass.states.get("media_player.tv").state == STATE_OFF
async def test_browse_media(hass: HomeAssistant): async def test_browse_media(hass: HomeAssistant) -> None:
"""Test browse media.""" """Test browse media."""
await async_setup_component( await async_setup_component(
hass, "media_player", {"media_player": {"platform": "demo"}} hass, "media_player", {"media_player": {"platform": "demo"}}
@@ -1133,7 +1133,7 @@ async def test_browse_media(hass: HomeAssistant):
assert result == MOCK_BROWSE_MEDIA assert result == MOCK_BROWSE_MEDIA
async def test_browse_media_override(hass: HomeAssistant): async def test_browse_media_override(hass: HomeAssistant) -> None:
"""Test browse media override.""" """Test browse media override."""
await async_setup_component( await async_setup_component(
hass, "media_player", {"media_player": {"platform": "demo"}} hass, "media_player", {"media_player": {"platform": "demo"}}

View File

@@ -3,8 +3,12 @@ from homeassistant.components.weather.const import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.typing import WebSocketGenerator
async def test_device_class_units(hass: HomeAssistant, hass_ws_client) -> None:
async def test_device_class_units(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test we can get supported units.""" """Test we can get supported units."""
assert await async_setup_component(hass, DOMAIN, {}) assert await async_setup_component(hass, DOMAIN, {})

View File

@@ -306,8 +306,8 @@ async def test_gateway_initialize_failure_transient(
], ],
) )
async def test_gateway_initialize_bellows_thread( async def test_gateway_initialize_bellows_thread(
device_path, thread_state, config_override, hass, coordinator device_path, thread_state, config_override, hass: HomeAssistant, coordinator
): ) -> None:
"""Test ZHA disabling the UART thread when connecting to a TCP coordinator.""" """Test ZHA disabling the UART thread when connecting to a TCP coordinator."""
zha_gateway = get_zha_gateway(hass) zha_gateway = get_zha_gateway(hass)
assert zha_gateway is not None assert zha_gateway is not None

View File

@@ -323,7 +323,7 @@ def test_weighted_match(
model, model,
quirk_class, quirk_class,
match_name, match_name,
): ) -> None:
"""Test weightedd match.""" """Test weightedd match."""
s = mock.sentinel s = mock.sentinel
@@ -435,7 +435,7 @@ def test_multi_sensor_match(channel, entity_registry: er.EntityRegistry) -> None
} }
def test_quirk_classes(): def test_quirk_classes() -> None:
"""Make sure that quirk_classes in components matches are valid.""" """Make sure that quirk_classes in components matches are valid."""
def find_quirk_class(base_obj, quirk_mod, quirk_cls): def find_quirk_class(base_obj, quirk_mod, quirk_cls):

View File

@@ -1460,8 +1460,8 @@ async def test_parse_qr_code_string(
async def test_try_parse_dsk_from_qr_code_string( async def test_try_parse_dsk_from_qr_code_string(
hass, integration, client, hass_ws_client hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
): ) -> None:
"""Test try_parse_dsk_from_qr_code_string websocket command.""" """Test try_parse_dsk_from_qr_code_string websocket command."""
entry = integration entry = integration
ws_client = await hass_ws_client(hass) ws_client = await hass_ws_client(hass)
@@ -1524,7 +1524,9 @@ async def test_try_parse_dsk_from_qr_code_string(
assert msg["error"]["code"] == ERR_NOT_LOADED assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_supports_feature(hass, integration, client, hass_ws_client): async def test_supports_feature(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test supports_feature websocket command.""" """Test supports_feature websocket command."""
entry = integration entry = integration
ws_client = await hass_ws_client(hass) ws_client = await hass_ws_client(hass)
@@ -3888,8 +3890,8 @@ async def test_subscribe_firmware_update_status_initial_value(
async def test_subscribe_controller_firmware_update_status( async def test_subscribe_controller_firmware_update_status(
hass, integration, client, hass_ws_client hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
): ) -> None:
"""Test the subscribe_firmware_update_status websocket command for a node.""" """Test the subscribe_firmware_update_status websocket command for a node."""
ws_client = await hass_ws_client(hass) ws_client = await hass_ws_client(hass)
device = get_device(hass, client.driver.controller.nodes[1]) device = get_device(hass, client.driver.controller.nodes[1])
@@ -3954,8 +3956,8 @@ async def test_subscribe_controller_firmware_update_status(
async def test_subscribe_controller_firmware_update_status_initial_value( async def test_subscribe_controller_firmware_update_status_initial_value(
hass, client, integration, hass_ws_client hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
): ) -> None:
"""Test subscribe_firmware_update_status cmd with in progress update for node.""" """Test subscribe_firmware_update_status cmd with in progress update for node."""
ws_client = await hass_ws_client(hass) ws_client = await hass_ws_client(hass)
device = get_device(hass, client.driver.controller.nodes[1]) device = get_device(hass, client.driver.controller.nodes[1])

View File

@@ -103,7 +103,9 @@ async def test_dynamic_climate_data_discovery_template_failure(
) )
async def test_merten_507801(hass, client, merten_507801, integration): async def test_merten_507801(
hass: HomeAssistant, client, merten_507801, integration
) -> None:
"""Test that Merten 507801 multilevel switch value is discovered as a cover.""" """Test that Merten 507801 multilevel switch value is discovered as a cover."""
node = merten_507801 node = merten_507801
assert node.device_class.specific.label == "Unused" assert node.device_class.specific.label == "Unused"
@@ -116,8 +118,8 @@ async def test_merten_507801(hass, client, merten_507801, integration):
async def test_merten_507801_disabled_enitites( async def test_merten_507801_disabled_enitites(
hass, client, merten_507801, integration hass: HomeAssistant, client, merten_507801, integration
): ) -> None:
"""Test that Merten 507801 entities created by endpoint 2 are disabled.""" """Test that Merten 507801 entities created by endpoint 2 are disabled."""
registry = er.async_get(hass) registry = er.async_get(hass)
entity_ids = [ entity_ids = [

View File

@@ -223,7 +223,7 @@ def area_mock(hass):
) )
async def test_call_from_config(hass: HomeAssistant): async def test_call_from_config(hass: HomeAssistant) -> None:
"""Test the sync wrapper of service.async_call_from_config.""" """Test the sync wrapper of service.async_call_from_config."""
calls = async_mock_service(hass, "test_domain", "test_service") calls = async_mock_service(hass, "test_domain", "test_service")
config = { config = {
@@ -238,7 +238,7 @@ async def test_call_from_config(hass: HomeAssistant):
assert calls[0].data == {"hello": "goodbye", "entity_id": ["hello.world"]} assert calls[0].data == {"hello": "goodbye", "entity_id": ["hello.world"]}
async def test_service_call(hass: HomeAssistant): async def test_service_call(hass: HomeAssistant) -> None:
"""Test service call with templating.""" """Test service call with templating."""
calls = async_mock_service(hass, "test_domain", "test_service") calls = async_mock_service(hass, "test_domain", "test_service")
config = { config = {
@@ -307,7 +307,7 @@ async def test_service_call(hass: HomeAssistant):
} }
async def test_service_template_service_call(hass: HomeAssistant): async def test_service_template_service_call(hass: HomeAssistant) -> None:
"""Test legacy service_template call with templating.""" """Test legacy service_template call with templating."""
calls = async_mock_service(hass, "test_domain", "test_service") calls = async_mock_service(hass, "test_domain", "test_service")
config = { config = {
@@ -322,7 +322,7 @@ async def test_service_template_service_call(hass: HomeAssistant):
assert calls[0].data == {"hello": "goodbye", "entity_id": ["hello.world"]} assert calls[0].data == {"hello": "goodbye", "entity_id": ["hello.world"]}
async def test_passing_variables_to_templates(hass: HomeAssistant): async def test_passing_variables_to_templates(hass: HomeAssistant) -> None:
"""Test passing variables to templates.""" """Test passing variables to templates."""
calls = async_mock_service(hass, "test_domain", "test_service") calls = async_mock_service(hass, "test_domain", "test_service")
config = { config = {
@@ -344,7 +344,7 @@ async def test_passing_variables_to_templates(hass: HomeAssistant):
assert calls[0].data == {"hello": "goodbye", "entity_id": ["hello.world"]} assert calls[0].data == {"hello": "goodbye", "entity_id": ["hello.world"]}
async def test_bad_template(hass: HomeAssistant): async def test_bad_template(hass: HomeAssistant) -> None:
"""Test passing bad template.""" """Test passing bad template."""
calls = async_mock_service(hass, "test_domain", "test_service") calls = async_mock_service(hass, "test_domain", "test_service")
config = { config = {
@@ -366,7 +366,7 @@ async def test_bad_template(hass: HomeAssistant):
assert len(calls) == 0 assert len(calls) == 0
async def test_split_entity_string(hass: HomeAssistant): async def test_split_entity_string(hass: HomeAssistant) -> None:
"""Test splitting of entity string.""" """Test splitting of entity string."""
calls = async_mock_service(hass, "test_domain", "test_service") calls = async_mock_service(hass, "test_domain", "test_service")
await service.async_call_from_config( await service.async_call_from_config(
@@ -380,7 +380,7 @@ async def test_split_entity_string(hass: HomeAssistant):
assert ["hello.world", "sensor.beer"] == calls[-1].data.get("entity_id") assert ["hello.world", "sensor.beer"] == calls[-1].data.get("entity_id")
async def test_not_mutate_input(hass: HomeAssistant): async def test_not_mutate_input(hass: HomeAssistant) -> None:
"""Test for immutable input.""" """Test for immutable input."""
async_mock_service(hass, "test_domain", "test_service") async_mock_service(hass, "test_domain", "test_service")
config = { config = {
@@ -403,7 +403,7 @@ async def test_not_mutate_input(hass: HomeAssistant):
@patch("homeassistant.helpers.service._LOGGER.error") @patch("homeassistant.helpers.service._LOGGER.error")
async def test_fail_silently_if_no_service(mock_log, hass: HomeAssistant): async def test_fail_silently_if_no_service(mock_log, hass: HomeAssistant) -> None:
"""Test failing if service is missing.""" """Test failing if service is missing."""
await service.async_call_from_config(hass, None) await service.async_call_from_config(hass, None)
assert mock_log.call_count == 1 assert mock_log.call_count == 1

View File

@@ -75,7 +75,7 @@ def test_async_add_hass_job_schedule_callback() -> None:
assert len(hass.add_job.mock_calls) == 0 assert len(hass.add_job.mock_calls) == 0
def test_async_add_hass_job_coro_named(hass) -> None: def test_async_add_hass_job_coro_named(hass: HomeAssistant) -> None:
"""Test that we schedule coroutines and add jobs to the job pool with a name.""" """Test that we schedule coroutines and add jobs to the job pool with a name."""
async def mycoro(): async def mycoro():

View File

@@ -492,7 +492,9 @@ def test_representing_yaml_loaded_data(
@pytest.mark.parametrize("hass_config_yaml", ["key: thing1\nkey: thing2"]) @pytest.mark.parametrize("hass_config_yaml", ["key: thing1\nkey: thing2"])
def test_duplicate_key(caplog, try_both_loaders, mock_hass_config_yaml: None) -> None: def test_duplicate_key(
caplog: pytest.LogCaptureFixture, try_both_loaders, mock_hass_config_yaml: None
) -> None:
"""Test duplicate dict keys.""" """Test duplicate dict keys."""
load_yaml_config_file(YAML_CONFIG_FILE) load_yaml_config_file(YAML_CONFIG_FILE)
assert "contains duplicate key" in caplog.text assert "contains duplicate key" in caplog.text
@@ -503,7 +505,7 @@ def test_duplicate_key(caplog, try_both_loaders, mock_hass_config_yaml: None) ->
[{YAML_CONFIG_FILE: "key: !secret a", yaml.SECRET_YAML: "a: 1\nb: !secret a"}], [{YAML_CONFIG_FILE: "key: !secret a", yaml.SECRET_YAML: "a: 1\nb: !secret a"}],
) )
def test_no_recursive_secrets( def test_no_recursive_secrets(
caplog, try_both_loaders, mock_hass_config_yaml: None caplog: pytest.LogCaptureFixture, try_both_loaders, mock_hass_config_yaml: None
) -> None: ) -> None:
"""Test that loading of secrets from the secrets file fails correctly.""" """Test that loading of secrets from the secrets file fails correctly."""
with pytest.raises(HomeAssistantError) as e: with pytest.raises(HomeAssistantError) as e: