mirror of
https://github.com/home-assistant/core.git
synced 2026-02-24 11:11:16 +01:00
Compare commits
97 Commits
backup_rea
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1f943ccda | ||
|
|
e37d84049a | ||
|
|
209473e376 | ||
|
|
334c3af448 | ||
|
|
5560139d24 | ||
|
|
d4dec5d1d3 | ||
|
|
6cb63a60bc | ||
|
|
991301e79e | ||
|
|
9c640fe0fa | ||
|
|
62145e5f9e | ||
|
|
c0fc414bb9 | ||
|
|
69411a05ff | ||
|
|
06c9ec861d | ||
|
|
946df1755f | ||
|
|
d0678e0641 | ||
|
|
ec56f183da | ||
|
|
033005e0de | ||
|
|
91f9f5a826 | ||
|
|
ac4fcab827 | ||
|
|
d0eea77178 | ||
|
|
fb38fa3844 | ||
|
|
440efb953e | ||
|
|
7ce47cca0d | ||
|
|
a5f607bb91 | ||
|
|
b03043aa6f | ||
|
|
0f3c7ca277 | ||
|
|
3abf7c22f3 | ||
|
|
292e1de126 | ||
|
|
2d776a8193 | ||
|
|
039bbbb48c | ||
|
|
ad5565df95 | ||
|
|
3e6bc29a6a | ||
|
|
ec8067a5a8 | ||
|
|
6f47716d0a | ||
|
|
efba5c6bcc | ||
|
|
d10e78079f | ||
|
|
6d4581580f | ||
|
|
0d9a41a540 | ||
|
|
cd69e6db73 | ||
|
|
1320367d0d | ||
|
|
dfa4698887 | ||
|
|
b426115de7 | ||
|
|
fb79fa37f8 | ||
|
|
6a5f7bf424 | ||
|
|
142ca6dec1 | ||
|
|
0f986c24d0 | ||
|
|
01f2b7b6f6 | ||
|
|
b9469027f5 | ||
|
|
fbb94af748 | ||
|
|
148bdf6e3a | ||
|
|
91999f8871 | ||
|
|
aecca4eb99 | ||
|
|
bf8aa49bae | ||
|
|
4423425683 | ||
|
|
44202da53d | ||
|
|
9f7dfb72c4 | ||
|
|
de07a69e4f | ||
|
|
bbf4c38115 | ||
|
|
e1bb5d52ef | ||
|
|
eb64b6bdee | ||
|
|
ecb288b735 | ||
|
|
a419c9c420 | ||
|
|
dd29133324 | ||
|
|
90f22ea516 | ||
|
|
9db1428265 | ||
|
|
a696b05b0d | ||
|
|
77ddb63b73 | ||
|
|
4180a6e176 | ||
|
|
6d74c912d2 | ||
|
|
8a01dfcc00 | ||
|
|
9722898dc6 | ||
|
|
7438c71fcb | ||
|
|
0b5e55b923 | ||
|
|
61ed959e8e | ||
|
|
3989532465 | ||
|
|
28027ddca4 | ||
|
|
fe0d7b3cca | ||
|
|
0dcc4e9527 | ||
|
|
b13b189703 | ||
|
|
150829f599 | ||
|
|
57dd9d9c23 | ||
|
|
e2056cb12c | ||
|
|
fa2c8992cf | ||
|
|
ddf5c7fe3a | ||
|
|
7034ed6d3f | ||
|
|
9015b53c1b | ||
|
|
1cfa6561f7 | ||
|
|
eead02dcca | ||
|
|
456e51a221 | ||
|
|
5d984ce186 | ||
|
|
61f45489ac | ||
|
|
f72c643b38 | ||
|
|
27bc26e886 | ||
|
|
0e9f03cbc1 | ||
|
|
9480c33fb0 | ||
|
|
3e6b8663e8 | ||
|
|
1c69a83793 |
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@@ -555,8 +555,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/fritz/ @AaronDavidSchneider @chemelli74 @mib1185
|
||||
/homeassistant/components/fritzbox/ @mib1185 @flabbamann
|
||||
/tests/components/fritzbox/ @mib1185 @flabbamann
|
||||
/homeassistant/components/fritzbox_callmonitor/ @cdce8p
|
||||
/tests/components/fritzbox_callmonitor/ @cdce8p
|
||||
/homeassistant/components/fronius/ @farmio
|
||||
/tests/components/fronius/ @farmio
|
||||
/homeassistant/components/frontend/ @home-assistant/frontend
|
||||
|
||||
@@ -300,16 +300,23 @@ class RuntimeEntryData:
|
||||
needed_platforms.add(Platform.BINARY_SENSOR)
|
||||
needed_platforms.add(Platform.SELECT)
|
||||
|
||||
needed_platforms.update(INFO_TYPE_TO_PLATFORM[type(info)] for info in infos)
|
||||
await self._ensure_platforms_loaded(hass, entry, needed_platforms)
|
||||
|
||||
# Make a dict of the EntityInfo by type and send
|
||||
# them to the listeners for each specific EntityInfo type
|
||||
info_types_to_platform = INFO_TYPE_TO_PLATFORM
|
||||
infos_by_type: defaultdict[type[EntityInfo], list[EntityInfo]] = defaultdict(
|
||||
list
|
||||
)
|
||||
for info in infos:
|
||||
infos_by_type[type(info)].append(info)
|
||||
info_type = type(info)
|
||||
if platform := info_types_to_platform.get(info_type):
|
||||
needed_platforms.add(platform)
|
||||
infos_by_type[info_type].append(info)
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Entity type %s is not supported in this version of Home Assistant",
|
||||
info_type,
|
||||
)
|
||||
await self._ensure_platforms_loaded(hass, entry, needed_platforms)
|
||||
|
||||
for type_, callbacks in self.entity_info_callbacks.items():
|
||||
# If all entities for a type are removed, we
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "fritzbox_callmonitor",
|
||||
"name": "FRITZ!Box Call Monitor",
|
||||
"codeowners": ["@cdce8p"],
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/fritzbox_callmonitor",
|
||||
"integration_type": "device",
|
||||
|
||||
@@ -6,6 +6,7 @@ from enum import Enum
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from bleak.backends.scanner import AdvertisementData
|
||||
from HueBLE import ConnectionError, HueBleError, HueBleLight, PairingError
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -26,6 +27,17 @@ from .light import get_available_color_modes
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SERVICE_UUID = SERVICE_DATA_UUID = "0000fe0f-0000-1000-8000-00805f9b34fb"
|
||||
|
||||
|
||||
def device_filter(advertisement_data: AdvertisementData) -> bool:
|
||||
"""Return True if the device is supported."""
|
||||
return (
|
||||
SERVICE_UUID in advertisement_data.service_uuids
|
||||
and SERVICE_DATA_UUID in advertisement_data.service_data
|
||||
)
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, address: str) -> Error | None:
|
||||
"""Return error if cannot connect and validate."""
|
||||
|
||||
@@ -70,28 +82,66 @@ class HueBleConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the config flow."""
|
||||
self._discovered_devices: dict[str, bluetooth.BluetoothServiceInfoBleak] = {}
|
||||
self._discovery_info: bluetooth.BluetoothServiceInfoBleak | None = None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the user step to pick discovered device."""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
unique_id = dr.format_mac(user_input[CONF_MAC])
|
||||
# Don't raise on progress because there may be discovery flows
|
||||
await self.async_set_unique_id(unique_id, raise_on_progress=False)
|
||||
# Guard against the user selecting a device which has been configured by
|
||||
# another flow.
|
||||
self._abort_if_unique_id_configured()
|
||||
self._discovery_info = self._discovered_devices[user_input[CONF_MAC]]
|
||||
return await self.async_step_confirm()
|
||||
|
||||
current_addresses = self._async_current_ids(include_ignore=False)
|
||||
for discovery in bluetooth.async_discovered_service_info(self.hass):
|
||||
if (
|
||||
discovery.address in current_addresses
|
||||
or discovery.address in self._discovered_devices
|
||||
or not device_filter(discovery.advertisement)
|
||||
):
|
||||
continue
|
||||
self._discovered_devices[discovery.address] = discovery
|
||||
|
||||
if not self._discovered_devices:
|
||||
return self.async_abort(reason="no_devices_found")
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_MAC): vol.In(
|
||||
{
|
||||
service_info.address: (
|
||||
f"{service_info.name} ({service_info.address})"
|
||||
)
|
||||
for service_info in self._discovered_devices.values()
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=data_schema,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_bluetooth(
|
||||
self, discovery_info: bluetooth.BluetoothServiceInfoBleak
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by the home assistant scanner."""
|
||||
|
||||
_LOGGER.debug(
|
||||
"HA found light %s. Will show in UI but not auto connect",
|
||||
"HA found light %s. Use user flow to show in UI and connect",
|
||||
discovery_info.name,
|
||||
)
|
||||
|
||||
unique_id = dr.format_mac(discovery_info.address)
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
name = f"{discovery_info.name} ({discovery_info.address})"
|
||||
self.context.update({"title_placeholders": {CONF_NAME: name}})
|
||||
|
||||
self._discovery_info = discovery_info
|
||||
|
||||
return await self.async_step_confirm()
|
||||
return self.async_abort(reason="discovery_unsupported")
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -103,7 +153,10 @@ class HueBleConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
if user_input is not None:
|
||||
unique_id = dr.format_mac(self._discovery_info.address)
|
||||
await self.async_set_unique_id(unique_id)
|
||||
# Don't raise on progress because there may be discovery flows
|
||||
await self.async_set_unique_id(unique_id, raise_on_progress=False)
|
||||
# Guard against the user selecting a device which has been configured by
|
||||
# another flow.
|
||||
self._abort_if_unique_id_configured()
|
||||
error = await validate_input(self.hass, unique_id)
|
||||
if error:
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"not_implemented": "This integration can only be set up via discovery."
|
||||
"discovery_unsupported": "Discovery flow is not supported by the Hue BLE integration.",
|
||||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
@@ -14,7 +15,16 @@
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Do you want to set up {name} ({mac})?. Make sure the light is [made discoverable to voice assistants]({url_pairing_mode}) or has been [factory reset]({url_factory_reset})."
|
||||
"description": "Do you want to set up {name} ({mac})?\nMake sure the light is [made discoverable to voice assistants]({url_pairing_mode}) or has been [factory reset]({url_factory_reset})."
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"mac": "[%key:common::config_flow::data::device%]"
|
||||
},
|
||||
"data_description": {
|
||||
"mac": "Select the Hue device you want to set up"
|
||||
},
|
||||
"description": "[%key:component::bluetooth::config::step::user::description%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,14 +109,18 @@ class LunatoneLight(
|
||||
return self._device is not None and self._device.is_on
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
def brightness(self) -> int | None:
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return value_to_brightness(self.BRIGHTNESS_SCALE, self._device.brightness)
|
||||
return (
|
||||
value_to_brightness(self.BRIGHTNESS_SCALE, self._device.brightness)
|
||||
if self._device.brightness is not None
|
||||
else None
|
||||
)
|
||||
|
||||
@property
|
||||
def color_mode(self) -> ColorMode:
|
||||
"""Return the color mode of the light."""
|
||||
if self._device is not None and self._device.is_dimmable:
|
||||
if self._device is not None and self._device.brightness is not None:
|
||||
return ColorMode.BRIGHTNESS
|
||||
return ColorMode.ONOFF
|
||||
|
||||
@@ -149,7 +153,8 @@ class LunatoneLight(
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Instruct the light to turn off."""
|
||||
if brightness_supported(self.supported_color_modes):
|
||||
self._last_brightness = self.brightness
|
||||
if self.brightness:
|
||||
self._last_brightness = self.brightness
|
||||
await self._device.fade_to_brightness(0)
|
||||
else:
|
||||
await self._device.switch_off()
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["lunatone-rest-api-client==0.6.3"]
|
||||
"requirements": ["lunatone-rest-api-client==0.7.0"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["hass_splunk"],
|
||||
"quality_scale": "legacy",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["hass-splunk==0.1.4"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -18,18 +18,9 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
Integration does not provide custom actions.
|
||||
docs-high-level-description:
|
||||
status: todo
|
||||
comment: |
|
||||
Verify integration docs at https://www.home-assistant.io/integrations/splunk/ include a high-level description of Splunk with a link to https://www.splunk.com/ and explain the integration's purpose for users unfamiliar with Splunk.
|
||||
docs-installation-instructions:
|
||||
status: todo
|
||||
comment: |
|
||||
Verify integration docs include clear prerequisites and step-by-step setup instructions including how to configure Splunk HTTP Event Collector and obtain the required token.
|
||||
docs-removal-instructions:
|
||||
status: todo
|
||||
comment: |
|
||||
Verify integration docs include instructions on how to remove the integration and clarify what happens to data already in Splunk.
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from abc import abstractmethod
|
||||
import asyncio
|
||||
from collections.abc import Callable, Sequence
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
@@ -430,48 +430,35 @@ class TelegramNotificationService:
|
||||
params[ATTR_PARSER] = None
|
||||
return params
|
||||
|
||||
async def _send_msgs(
|
||||
async def _send_msg_formatted(
|
||||
self,
|
||||
func_send: Callable,
|
||||
func_send: Callable[..., Awaitable[Message]],
|
||||
message_tag: str | None,
|
||||
*args_msg: Any,
|
||||
context: Context | None = None,
|
||||
**kwargs_msg: Any,
|
||||
) -> dict[str, JsonValueType]:
|
||||
"""Sends a message to each of the targets.
|
||||
|
||||
If there is only 1 targtet, an error is raised if the send fails.
|
||||
For multiple targets, errors are logged and the caller is responsible for checking which target is successful/failed based on the return value.
|
||||
"""Sends a message and formats the response.
|
||||
|
||||
:return: dict with chat_id keys and message_id values for successful sends
|
||||
"""
|
||||
chat_ids = [kwargs_msg.pop(ATTR_CHAT_ID)]
|
||||
msg_ids: dict[str, JsonValueType] = {}
|
||||
for chat_id in chat_ids:
|
||||
_LOGGER.debug("%s to chat ID %s", func_send.__name__, chat_id)
|
||||
chat_id: int = kwargs_msg.pop(ATTR_CHAT_ID)
|
||||
_LOGGER.debug("%s to chat ID %s", func_send.__name__, chat_id)
|
||||
|
||||
for file_type in _FILE_TYPES:
|
||||
if file_type in kwargs_msg and isinstance(
|
||||
kwargs_msg[file_type], io.BytesIO
|
||||
):
|
||||
kwargs_msg[file_type].seek(0)
|
||||
response: Message = await self._send_msg(
|
||||
func_send,
|
||||
message_tag,
|
||||
chat_id,
|
||||
*args_msg,
|
||||
context=context,
|
||||
**kwargs_msg,
|
||||
)
|
||||
|
||||
response: Message = await self._send_msg(
|
||||
func_send,
|
||||
message_tag,
|
||||
chat_id,
|
||||
*args_msg,
|
||||
context=context,
|
||||
**kwargs_msg,
|
||||
)
|
||||
if response:
|
||||
msg_ids[str(chat_id)] = response.id
|
||||
|
||||
return msg_ids
|
||||
return {str(chat_id): response.id}
|
||||
|
||||
async def _send_msg(
|
||||
self,
|
||||
func_send: Callable,
|
||||
func_send: Callable[..., Awaitable[Any]],
|
||||
message_tag: str | None,
|
||||
*args_msg: Any,
|
||||
context: Context | None = None,
|
||||
@@ -518,7 +505,7 @@ class TelegramNotificationService:
|
||||
title = kwargs.get(ATTR_TITLE)
|
||||
text = f"{title}\n{message}" if title else message
|
||||
params = self._get_msg_kwargs(kwargs)
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_message,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
text,
|
||||
@@ -759,7 +746,7 @@ class TelegramNotificationService:
|
||||
)
|
||||
|
||||
if file_type == SERVICE_SEND_PHOTO:
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_photo,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
@@ -775,7 +762,7 @@ class TelegramNotificationService:
|
||||
)
|
||||
|
||||
if file_type == SERVICE_SEND_STICKER:
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_sticker,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
@@ -789,7 +776,7 @@ class TelegramNotificationService:
|
||||
)
|
||||
|
||||
if file_type == SERVICE_SEND_VIDEO:
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_video,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
@@ -805,7 +792,7 @@ class TelegramNotificationService:
|
||||
)
|
||||
|
||||
if file_type == SERVICE_SEND_DOCUMENT:
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_document,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
@@ -821,7 +808,7 @@ class TelegramNotificationService:
|
||||
)
|
||||
|
||||
if file_type == SERVICE_SEND_VOICE:
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_voice,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
@@ -836,7 +823,7 @@ class TelegramNotificationService:
|
||||
)
|
||||
|
||||
# SERVICE_SEND_ANIMATION
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_animation,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
@@ -861,7 +848,7 @@ class TelegramNotificationService:
|
||||
stickerid = kwargs.get(ATTR_STICKER_ID)
|
||||
|
||||
if stickerid:
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_sticker,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
@@ -886,7 +873,7 @@ class TelegramNotificationService:
|
||||
latitude = float(latitude)
|
||||
longitude = float(longitude)
|
||||
params = self._get_msg_kwargs(kwargs)
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_location,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
@@ -911,7 +898,7 @@ class TelegramNotificationService:
|
||||
"""Send a poll."""
|
||||
params = self._get_msg_kwargs(kwargs)
|
||||
openperiod = kwargs.get(ATTR_OPEN_PERIOD)
|
||||
return await self._send_msgs(
|
||||
return await self._send_msg_formatted(
|
||||
self.bot.send_poll,
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
|
||||
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@@ -1452,7 +1452,7 @@ loqedAPI==2.1.10
|
||||
luftdaten==0.7.4
|
||||
|
||||
# homeassistant.components.lunatone
|
||||
lunatone-rest-api-client==0.6.3
|
||||
lunatone-rest-api-client==0.7.0
|
||||
|
||||
# homeassistant.components.lupusec
|
||||
lupupy==0.3.2
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -1271,7 +1271,7 @@ loqedAPI==2.1.10
|
||||
luftdaten==0.7.4
|
||||
|
||||
# homeassistant.components.lunatone
|
||||
lunatone-rest-api-client==0.6.3
|
||||
lunatone-rest-api-client==0.7.0
|
||||
|
||||
# homeassistant.components.lupusec
|
||||
lupupy==0.3.2
|
||||
|
||||
@@ -1895,7 +1895,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
|
||||
"spc",
|
||||
"speedtestdotnet",
|
||||
"spider",
|
||||
"splunk",
|
||||
"spotify",
|
||||
"sql",
|
||||
"srp_energy",
|
||||
|
||||
@@ -5,9 +5,11 @@ from unittest.mock import Mock, patch
|
||||
from aioesphomeapi import (
|
||||
APIClient,
|
||||
EntityCategory as ESPHomeEntityCategory,
|
||||
EntityInfo,
|
||||
SensorInfo,
|
||||
SensorState,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.esphome import DOMAIN
|
||||
from homeassistant.components.esphome.entry_data import RuntimeEntryData
|
||||
@@ -152,3 +154,42 @@ async def test_discover_zwave_without_home_id() -> None:
|
||||
)
|
||||
# Verify async_create_flow was NOT called when zwave_home_id is 0
|
||||
mock_create_flow.assert_not_called()
|
||||
|
||||
|
||||
async def test_unknown_entity_type_skipped(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_generic_device_entry: MockGenericDeviceEntryType,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that unknown entity types are skipped gracefully."""
|
||||
|
||||
class UnknownInfo(EntityInfo):
|
||||
"""Mock unknown entity info type."""
|
||||
|
||||
entity_info = [
|
||||
SensorInfo(
|
||||
object_id="mysensor",
|
||||
key=1,
|
||||
name="my sensor",
|
||||
),
|
||||
UnknownInfo(
|
||||
object_id="unknown",
|
||||
key=2,
|
||||
name="unknown entity",
|
||||
),
|
||||
]
|
||||
states = [SensorState(key=1, state=42)]
|
||||
await mock_generic_device_entry(
|
||||
mock_client=mock_client,
|
||||
entity_info=entity_info,
|
||||
states=states,
|
||||
)
|
||||
|
||||
assert "UnknownInfo" in caplog.text
|
||||
assert "not supported in this version of Home Assistant" in caplog.text
|
||||
|
||||
# Known entity still works
|
||||
state = hass.states.get("sensor.test_my_sensor")
|
||||
assert state is not None
|
||||
assert state.state == "42"
|
||||
|
||||
@@ -42,3 +42,21 @@ HUE_BLE_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
connectable=True,
|
||||
tx_power=-127,
|
||||
)
|
||||
|
||||
NOT_HUE_BLE_DISCOVERY_INFO = BluetoothServiceInfoBleak(
|
||||
name="Not",
|
||||
address="AA:BB:CC:DD:EE:F2",
|
||||
rssi=-60,
|
||||
manufacturer_data={
|
||||
33: b"\x00\x00\xd1\xf0b;\xd8\x1dE\xd6\xba\xeeL\xdd]\xf5\xb2\xe9",
|
||||
21: b"\x061\x00Z\x8f\x93\xb2\xec\x85\x06\x00i\x00\x02\x02Q\xed\x1d\xf0",
|
||||
},
|
||||
service_uuids=[],
|
||||
service_data={},
|
||||
source="local",
|
||||
device=generate_ble_device(address="AA:BB:CC:DD:EE:F2", name="Aug"),
|
||||
advertisement=generate_advertisement_data(),
|
||||
time=0,
|
||||
connectable=True,
|
||||
tx_power=-127,
|
||||
)
|
||||
|
||||
@@ -2,23 +2,28 @@
|
||||
|
||||
from unittest.mock import AsyncMock, PropertyMock, patch
|
||||
|
||||
from habluetooth import BluetoothServiceInfoBleak
|
||||
from HueBLE import ConnectionError, HueBleError, PairingError
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.hue_ble.config_flow import Error
|
||||
from homeassistant.components.hue_ble.const import (
|
||||
DOMAIN,
|
||||
URL_FACTORY_RESET,
|
||||
URL_PAIRING_MODE,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_BLUETOOTH
|
||||
from homeassistant.config_entries import SOURCE_BLUETOOTH, SOURCE_USER
|
||||
from homeassistant.const import CONF_MAC, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from . import HUE_BLE_SERVICE_INFO, TEST_DEVICE_MAC, TEST_DEVICE_NAME
|
||||
from . import (
|
||||
HUE_BLE_SERVICE_INFO,
|
||||
NOT_HUE_BLE_DISCOVERY_INFO,
|
||||
TEST_DEVICE_MAC,
|
||||
TEST_DEVICE_NAME,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.bluetooth import BLEDevice, generate_ble_device
|
||||
@@ -27,17 +32,34 @@ AUTH_ERROR = ConnectionError()
|
||||
AUTH_ERROR.__cause__ = PairingError()
|
||||
|
||||
|
||||
async def test_bluetooth_form(
|
||||
async def test_user_form(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test bluetooth discovery form."""
|
||||
"""Test user form."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_BLUETOOTH},
|
||||
data=HUE_BLE_SERVICE_INFO,
|
||||
with patch(
|
||||
"homeassistant.components.hue_ble.config_flow.bluetooth.async_discovered_service_info",
|
||||
return_value=[NOT_HUE_BLE_DISCOVERY_INFO, HUE_BLE_SERVICE_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["data_schema"].schema[CONF_MAC].container == {
|
||||
HUE_BLE_SERVICE_INFO.address: (
|
||||
f"{HUE_BLE_SERVICE_INFO.name} ({HUE_BLE_SERVICE_INFO.address})"
|
||||
),
|
||||
}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_MAC: HUE_BLE_SERVICE_INFO.address},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm"
|
||||
assert result["description_placeholders"] == {
|
||||
@@ -78,6 +100,27 @@ async def test_bluetooth_form(
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("discovery_info", [[NOT_HUE_BLE_DISCOVERY_INFO], []])
|
||||
async def test_user_form_no_device(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
discovery_info: list[BluetoothServiceInfoBleak],
|
||||
) -> None:
|
||||
"""Test user form with no devices."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hue_ble.config_flow.bluetooth.async_discovered_service_info",
|
||||
return_value=discovery_info,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "no_devices_found"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"mock_return_device",
|
||||
@@ -155,7 +198,7 @@ async def test_bluetooth_form(
|
||||
"unknown",
|
||||
],
|
||||
)
|
||||
async def test_bluetooth_form_exception(
|
||||
async def test_user_form_exception(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_return_device: BLEDevice | None,
|
||||
@@ -165,13 +208,30 @@ async def test_bluetooth_form_exception(
|
||||
mock_poll_state: Exception | None,
|
||||
error: Error,
|
||||
) -> None:
|
||||
"""Test bluetooth discovery form with errors."""
|
||||
"""Test user form with errors."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_BLUETOOTH},
|
||||
data=HUE_BLE_SERVICE_INFO,
|
||||
with patch(
|
||||
"homeassistant.components.hue_ble.config_flow.bluetooth.async_discovered_service_info",
|
||||
return_value=[NOT_HUE_BLE_DISCOVERY_INFO, HUE_BLE_SERVICE_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["data_schema"].schema[CONF_MAC].container == {
|
||||
HUE_BLE_SERVICE_INFO.address: (
|
||||
f"{HUE_BLE_SERVICE_INFO.name} ({HUE_BLE_SERVICE_INFO.address})"
|
||||
),
|
||||
}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_MAC: HUE_BLE_SERVICE_INFO.address},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm"
|
||||
|
||||
@@ -232,17 +292,19 @@ async def test_bluetooth_form_exception(
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_user_form_exception(
|
||||
async def test_bluetooth_discovery_aborts(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test the user form raises a discovery only error."""
|
||||
"""Test bluetooth form aborts."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_BLUETOOTH},
|
||||
data=HUE_BLE_SERVICE_INFO,
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "not_implemented"
|
||||
assert result["reason"] == "discovery_unsupported"
|
||||
|
||||
|
||||
async def test_bluetooth_form_exception_already_set_up(
|
||||
@@ -260,4 +322,38 @@ async def test_bluetooth_form_exception_already_set_up(
|
||||
data=HUE_BLE_SERVICE_INFO,
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "discovery_unsupported"
|
||||
|
||||
|
||||
async def test_user_form_exception_already_set_up(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test user form when device is already set up."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hue_ble.config_flow.bluetooth.async_discovered_service_info",
|
||||
return_value=[NOT_HUE_BLE_DISCOVERY_INFO, HUE_BLE_SERVICE_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["data_schema"].schema[CONF_MAC].container == {
|
||||
HUE_BLE_SERVICE_INFO.address: (
|
||||
f"{HUE_BLE_SERVICE_INFO.name} ({HUE_BLE_SERVICE_INFO.address})"
|
||||
),
|
||||
}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_MAC: HUE_BLE_SERVICE_INFO.address},
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
@@ -11,7 +11,7 @@ from lunatone_rest_api_client.models import (
|
||||
InfoData,
|
||||
LineStatus,
|
||||
)
|
||||
from lunatone_rest_api_client.models.common import ColorRGBData, ColorWAFData, Status
|
||||
from lunatone_rest_api_client.models.common import Status
|
||||
from lunatone_rest_api_client.models.devices import DeviceStatus
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -77,13 +77,7 @@ def build_device_data_list() -> list[DeviceData]:
|
||||
name="Device 1",
|
||||
available=True,
|
||||
status=DeviceStatus(),
|
||||
features=FeaturesStatus(
|
||||
switchable=Status[bool](status=False),
|
||||
dimmable=Status[float](status=0.0),
|
||||
colorKelvin=Status[int](status=1000),
|
||||
colorRGB=Status[ColorRGBData](status=ColorRGBData(r=0, g=0, b=0)),
|
||||
colorWAF=Status[ColorWAFData](status=ColorWAFData(w=0, a=0, f=0)),
|
||||
),
|
||||
features=FeaturesStatus(switchable=Status[bool](status=False)),
|
||||
address=0,
|
||||
line=0,
|
||||
),
|
||||
@@ -95,9 +89,6 @@ def build_device_data_list() -> list[DeviceData]:
|
||||
features=FeaturesStatus(
|
||||
switchable=Status[bool](status=False),
|
||||
dimmable=Status[float](status=0.0),
|
||||
colorKelvin=Status[int](status=1000),
|
||||
colorRGB=Status[ColorRGBData](status=ColorRGBData(r=0, g=0, b=0)),
|
||||
colorWAF=Status[ColorWAFData](status=ColorWAFData(w=0, a=0, f=0)),
|
||||
),
|
||||
address=1,
|
||||
line=0,
|
||||
|
||||
@@ -27,7 +27,6 @@ def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
@pytest.fixture
|
||||
def mock_lunatone_devices() -> Generator[AsyncMock]:
|
||||
"""Mock a Lunatone devices object."""
|
||||
state = {"is_dimmable": False}
|
||||
|
||||
def build_devices_mock(devices: Devices):
|
||||
device_list = []
|
||||
@@ -39,9 +38,10 @@ def mock_lunatone_devices() -> Generator[AsyncMock]:
|
||||
device.id = device.data.id
|
||||
device.name = device.data.name
|
||||
device.is_on = device.data.features.switchable.status
|
||||
device.brightness = device.data.features.dimmable.status
|
||||
type(device).is_dimmable = PropertyMock(
|
||||
side_effect=lambda s=state: s["is_dimmable"]
|
||||
device.brightness = (
|
||||
device.data.features.dimmable.status
|
||||
if device.data.features.dimmable
|
||||
else None
|
||||
)
|
||||
device_list.append(device)
|
||||
return device_list
|
||||
@@ -54,7 +54,6 @@ def mock_lunatone_devices() -> Generator[AsyncMock]:
|
||||
type(devices).devices = PropertyMock(
|
||||
side_effect=lambda d=devices: build_devices_mock(d)
|
||||
)
|
||||
devices.set_is_dimmable = lambda value, s=state: s.update(is_dimmable=value)
|
||||
yield devices
|
||||
|
||||
|
||||
|
||||
@@ -8,34 +8,18 @@
|
||||
'dali_types': list([
|
||||
]),
|
||||
'features': dict({
|
||||
'color_kelvin': dict({
|
||||
'status': 1000.0,
|
||||
}),
|
||||
'color_kelvin': None,
|
||||
'color_kelvin_with_fade': None,
|
||||
'color_rgb': dict({
|
||||
'status': dict({
|
||||
'blue': 0.0,
|
||||
'green': 0.0,
|
||||
'red': 0.0,
|
||||
}),
|
||||
}),
|
||||
'color_rgb': None,
|
||||
'color_rgb_with_fade': None,
|
||||
'color_waf': dict({
|
||||
'status': dict({
|
||||
'amber': 0.0,
|
||||
'free_color': 0.0,
|
||||
'white': 0.0,
|
||||
}),
|
||||
}),
|
||||
'color_waf': None,
|
||||
'color_waf_with_fade': None,
|
||||
'color_xy': None,
|
||||
'color_xy_with_fade': None,
|
||||
'dali_cmd16': None,
|
||||
'dim_down': None,
|
||||
'dim_up': None,
|
||||
'dimmable': dict({
|
||||
'status': 0.0,
|
||||
}),
|
||||
'dimmable': None,
|
||||
'dimmable_kelvin': None,
|
||||
'dimmable_rgb': None,
|
||||
'dimmable_waf': None,
|
||||
@@ -79,25 +63,11 @@
|
||||
'dali_types': list([
|
||||
]),
|
||||
'features': dict({
|
||||
'color_kelvin': dict({
|
||||
'status': 1000.0,
|
||||
}),
|
||||
'color_kelvin': None,
|
||||
'color_kelvin_with_fade': None,
|
||||
'color_rgb': dict({
|
||||
'status': dict({
|
||||
'blue': 0.0,
|
||||
'green': 0.0,
|
||||
'red': 0.0,
|
||||
}),
|
||||
}),
|
||||
'color_rgb': None,
|
||||
'color_rgb_with_fade': None,
|
||||
'color_waf': dict({
|
||||
'status': dict({
|
||||
'amber': 0.0,
|
||||
'free_color': 0.0,
|
||||
'white': 0.0,
|
||||
}),
|
||||
}),
|
||||
'color_waf': None,
|
||||
'color_waf_with_fade': None,
|
||||
'color_xy': None,
|
||||
'color_xy_with_fade': None,
|
||||
@@ -208,6 +178,7 @@
|
||||
'node_red': False,
|
||||
'startup_mode': 'normal',
|
||||
'tier': 'basic',
|
||||
'uid': None,
|
||||
'version': 'v1.14.1/1.4.3',
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.ONOFF: 'onoff'>,
|
||||
<ColorMode.BRIGHTNESS: 'brightness'>,
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -100,10 +100,11 @@
|
||||
# name: test_setup[light.device_2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'brightness': None,
|
||||
'color_mode': None,
|
||||
'friendly_name': 'Device 2',
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.ONOFF: 'onoff'>,
|
||||
<ColorMode.BRIGHTNESS: 'brightness'>,
|
||||
]),
|
||||
'supported_features': <LightEntityFeature: 0>,
|
||||
}),
|
||||
|
||||
@@ -22,8 +22,6 @@ from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
TEST_ENTITY_ID = "light.device_1"
|
||||
|
||||
|
||||
async def test_setup(
|
||||
hass: HomeAssistant,
|
||||
@@ -52,10 +50,13 @@ async def test_turn_on_off(
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the light can be turned on and off."""
|
||||
device_id = 1
|
||||
entity_id = f"light.device_{device_id}"
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
async def fake_update():
|
||||
device = mock_lunatone_devices.data.devices[0]
|
||||
device = mock_lunatone_devices.data.devices[device_id - 1]
|
||||
device.features.switchable.status = not device.features.switchable.status
|
||||
|
||||
mock_lunatone_devices.async_update.side_effect = fake_update
|
||||
@@ -63,22 +64,22 @@ async def test_turn_on_off(
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(TEST_ENTITY_ID)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(TEST_ENTITY_ID)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
@@ -90,16 +91,16 @@ async def test_turn_on_off_with_brightness(
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the light can be turned on with brightness."""
|
||||
device_id = 2
|
||||
entity_id = f"light.device_{device_id}"
|
||||
expected_brightness = 128
|
||||
brightness_percentages = iter([50.0, 0.0, 50.0])
|
||||
|
||||
mock_lunatone_devices.set_is_dimmable(True)
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
async def fake_update():
|
||||
brightness = next(brightness_percentages)
|
||||
device = mock_lunatone_devices.data.devices[0]
|
||||
device = mock_lunatone_devices.data.devices[device_id - 1]
|
||||
device.features.switchable.status = brightness > 0
|
||||
device.features.dimmable.status = brightness
|
||||
|
||||
@@ -108,11 +109,11 @@ async def test_turn_on_off_with_brightness(
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_BRIGHTNESS: expected_brightness},
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: expected_brightness},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(TEST_ENTITY_ID)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes["brightness"] == expected_brightness
|
||||
@@ -120,11 +121,11 @@ async def test_turn_on_off_with_brightness(
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(TEST_ENTITY_ID)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert not state.attributes["brightness"]
|
||||
@@ -132,11 +133,11 @@ async def test_turn_on_off_with_brightness(
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(TEST_ENTITY_ID)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes["brightness"] == expected_brightness
|
||||
|
||||
Binary file not shown.
BIN
tests/fixtures/core/backup_restore/backup_with_database_protected_v2.tar
vendored
Normal file
BIN
tests/fixtures/core/backup_restore/backup_with_database_protected_v2.tar
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/core/backup_restore/backup_with_database_protected_v3.tar
vendored
Normal file
BIN
tests/fixtures/core/backup_restore/backup_with_database_protected_v3.tar
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/core/backup_restore/malicious_backup_with_database.tar
vendored
Normal file
BIN
tests/fixtures/core/backup_restore/malicious_backup_with_database.tar
vendored
Normal file
Binary file not shown.
@@ -240,6 +240,14 @@ def test_aborting_for_older_versions(restore_config: str, tmp_path: Path) -> Non
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("backup", "password"),
|
||||
[
|
||||
("backup_with_database.tar", None),
|
||||
("backup_with_database_protected_v2.tar", "hunter2"),
|
||||
("backup_with_database_protected_v3.tar", "hunter2"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"restore_backup_content",
|
||||
@@ -287,6 +295,8 @@ def test_aborting_for_older_versions(restore_config: str, tmp_path: Path) -> Non
|
||||
],
|
||||
)
|
||||
def test_restore_backup(
|
||||
backup: str,
|
||||
password: str | None,
|
||||
restore_backup_content: backup_restore.RestoreBackupFileContent,
|
||||
expected_kept_files: set[str],
|
||||
expected_restored_files: set[str],
|
||||
@@ -321,9 +331,7 @@ def test_restore_backup(
|
||||
for f in existing_files:
|
||||
(tmp_path / f).write_text("before_restore")
|
||||
|
||||
get_fixture_path(
|
||||
"core/backup_restore/empty_backup_database_included.tar", None
|
||||
).copy(backup_file_path)
|
||||
get_fixture_path(f"core/backup_restore/{backup}", None).copy(backup_file_path)
|
||||
|
||||
files_before_restore = get_files(tmp_path)
|
||||
assert files_before_restore == {
|
||||
@@ -341,6 +349,7 @@ def test_restore_backup(
|
||||
kept_files_data[file] = (tmp_path / file).read_bytes()
|
||||
|
||||
restore_backup_content.backup_file_path = backup_file_path
|
||||
restore_backup_content.password = password
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -378,7 +387,7 @@ def test_restore_backup_filter_files(tmp_path: Path) -> None:
|
||||
backup_file_path = tmp_path / "backups" / "test.tar"
|
||||
backup_file_path.parent.mkdir()
|
||||
get_fixture_path(
|
||||
"core/backup_restore/empty_backup_database_included.tar", None
|
||||
"core/backup_restore/malicious_backup_with_database.tar", None
|
||||
).copy(backup_file_path)
|
||||
|
||||
with (
|
||||
@@ -440,9 +449,9 @@ def test_remove_backup_file_after_restore(
|
||||
"""Test removing a backup file after restore."""
|
||||
backup_file_path = tmp_path / "backups" / "test.tar"
|
||||
backup_file_path.parent.mkdir()
|
||||
get_fixture_path(
|
||||
"core/backup_restore/empty_backup_database_included.tar", None
|
||||
).copy(backup_file_path)
|
||||
get_fixture_path("core/backup_restore/backup_with_database.tar", None).copy(
|
||||
backup_file_path
|
||||
)
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
|
||||
Reference in New Issue
Block a user