Compare commits

...

97 Commits

Author SHA1 Message Date
Erik Montnemery
b1f943ccda Replace discovery with user flow in Philips Hue BLE (#163924) 2026-02-24 11:06:31 +01:00
Brett Adams
e37d84049a Update Splunk integration to bronze quality scale (#163616)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-24 10:56:05 +01:00
Marc Mueller
209473e376 Remove myself as codeowner for fritzbox_callmonitor (#163927) 2026-02-24 10:45:58 +01:00
MoonDevLT
334c3af448 Bump lunatone-rest-api-client to 0.7.0 (#163594) 2026-02-24 10:10:04 +01:00
hanwg
5560139d24 Clean up duplicated code in Telegram bot (#163917) 2026-02-24 10:04:21 +01:00
Erik Montnemery
d4dec5d1d3 Improve backup_restore tests (#163921) 2026-02-24 10:03:42 +01:00
J. Nick Koston
6cb63a60bc Skip unknown entity types in ESPHome integration (#163887) 2026-02-24 08:48:27 +01:00
Franck Nijhof
991301e79e Merge branch 'master' into dev 2026-02-24 07:07:39 +00:00
Franck Nijhof
9c640fe0fa 2026.2.3 (#163683) 2026-02-20 21:43:32 +01:00
Sid
62145e5f9e Bump eheimdigital to 1.6.0 (#161961) 2026-02-20 19:51:10 +00:00
Franck Nijhof
c0fc414bb9 Fix nrgkick tests for rc 2026-02-20 19:49:27 +00:00
Franck Nijhof
69411a05ff Bump version to 2026.2.3 2026-02-20 19:39:05 +00:00
Marc Mueller
06c9ec861d Fix hassfest requirements check (#163681) 2026-02-20 19:38:58 +00:00
Joost Lekkerkerker
946df1755f Bump pySmartThings to 3.5.3 (#163375)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-02-20 19:38:56 +00:00
Thomas Sejr Madsen
d0678e0641 Fix touchline_sl zone availability when alarm state is set (#163338) 2026-02-20 19:38:55 +00:00
Allen Porter
ec56f183da Bump pyrainbird to 6.0.5 (#163333) 2026-02-20 19:38:53 +00:00
Åke Strandberg
033005e0de Add Miele dishwasher program code (#163308) 2026-02-20 19:38:52 +00:00
Andreas Jakl
91f9f5a826 NRGkick: do not update vehicle connected timestamp when vehicle is not connected (#163292) 2026-02-20 19:38:51 +00:00
David Recordon
ac4fcab827 Fix Control4 HVAC action mapping for multi-stage and idle states (#163222) 2026-02-20 19:38:49 +00:00
Allen Porter
d0eea77178 Fix remote calendar event handling of events within the same update period (#163186)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-20 19:38:48 +00:00
Markus Adrario
fb38fa3844 Add Lux to homee units (#163180) 2026-02-20 19:38:47 +00:00
Allen Porter
440efb953e Bump ical to 13.2.0 (#163123) 2026-02-20 19:38:45 +00:00
Manu
7ce47cca0d Fix blocking call in Xbox config flow (#163122) 2026-02-20 19:38:44 +00:00
Andre Lengwenus
a5f607bb91 Bump pypck to 0.9.11 (#163043) 2026-02-20 19:38:42 +00:00
Andre Lengwenus
b03043aa6f Bump pypck to 0.9.10 (#162333) 2026-02-20 19:38:41 +00:00
Robert Resch
0f3c7ca277 Block redirect to localhost (#162941) 2026-02-20 19:37:03 +00:00
Martin Hjelmare
3abf7c22f3 Fix Z-Wave climate set preset (#162728) 2026-02-20 19:37:01 +00:00
hbludworth
292e1de126 Show progress indicator during backup stage of Core/App update (#162683) 2026-02-20 19:37:00 +00:00
Christian Lackas
2d776a8193 Fix HomematicIP entity recovery after access point cloud reconnect (#162575) 2026-02-20 19:36:58 +00:00
Sid
039bbbb48c Fix dynamic entity creation in eheimdigital (#161155) 2026-02-20 19:36:56 +00:00
Luke Lashley
ad5565df95 Add the ability to select region for Roborock (#160898) 2026-02-20 19:36:55 +00:00
Franck Nijhof
3e6bc29a6a 2026.2.2 (#162950) 2026-02-13 21:05:06 +01:00
Franck Nijhof
ec8067a5a8 Bump version to 2026.2.2 2026-02-13 19:25:16 +00:00
Josef Zweck
6f47716d0a Log remaining token duration in onedrive (#162933) 2026-02-13 19:24:25 +00:00
puddly
efba5c6bcc Bump ZHA to 0.0.90 (#162894) 2026-02-13 19:24:24 +00:00
Sammy [Andrei Marinache]
d10e78079f Add Miele TQ1000WP tumble dryer programs and program phases (#162871)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Åke Strandberg <ake@strandberg.eu>
2026-02-13 19:24:23 +00:00
Jon Seager
6d4581580f Bump pytouchlinesl to 0.6.0 (#162856) 2026-02-13 19:24:21 +00:00
Yoshi Walsh
0d9a41a540 Bump pydaikin to 2.17.2 (#162846) 2026-02-13 19:24:20 +00:00
Vicx
cd69e6db73 Bump slixmpp to 1.13.2 (#162837) 2026-02-13 19:24:19 +00:00
Xitee
1320367d0d Filter out transient zero values from qBittorrent alltime stats (#162821)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 19:24:18 +00:00
Joost Lekkerkerker
dfa4698887 Bump pySmartThings to 3.5.2 (#162809)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-02-13 19:24:17 +00:00
Robert Resch
b426115de7 Bump cryptography to 46.0.5 (#162783) 2026-02-13 19:24:15 +00:00
hanwg
fb79fa37f8 Fix bug in edit_message_media action for Telegram bot (#162762) 2026-02-13 19:24:14 +00:00
Simone Chemelli
6a5f7bf424 Fix image platform state for Vodafone Station (#162747)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-13 19:24:13 +00:00
Simone Chemelli
142ca6dec1 Fix alarm refresh warning for Comelit SimpleHome (#162710) 2026-02-13 19:24:12 +00:00
epenet
0f986c24d0 Fix unavailable status in Tuya (#162709) 2026-02-13 19:24:11 +00:00
Josef Zweck
01f2b7b6f6 Bump onedrive-personal-sdk to 0.1.2 (#162689) 2026-02-13 19:24:09 +00:00
Michael
b9469027f5 Fix handling when FRITZ!Box reboots in FRITZ!Box Tools (#162679) 2026-02-13 19:24:08 +00:00
Tomás Correia
fbb94af748 fix to cloudflare r2 setup screen info (#162677) 2026-02-13 19:24:07 +00:00
Michael
148bdf6e3a Fix handling when FRITZ!Box reboots in FRITZ!Smarthome (#162676) 2026-02-13 19:24:05 +00:00
starkillerOG
91999f8871 Bump reolink-aio to 0.19.0 (#162672) 2026-02-13 19:24:04 +00:00
Jeef
aecca4eb99 Bump intellifire4py to 4.3.1 (#162659) 2026-02-13 19:24:03 +00:00
Allen Porter
bf8aa49bae Improve MCP SSE fallback error handling (#162655) 2026-02-13 19:24:02 +00:00
Joost Lekkerkerker
4423425683 Pin setuptools to 81.0.0 (#162589)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 19:24:01 +00:00
Aaron Godfrey
44202da53d Increase max tasks retrieved per page to prevent timeout (#162587) 2026-02-13 19:23:59 +00:00
Thomas55555
9f7dfb72c4 Bump aioautomower to 2.7.3 (#162583)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-13 19:23:58 +00:00
Michael
de07a69e4f Bump aioimmich to 0.12.0 (#162573)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-13 19:23:57 +00:00
Maikel Punie
bbf4c38115 migrate velbus config entries (#162565) 2026-02-13 19:23:56 +00:00
ElCruncharino
e1bb5d52ef Add timeout to B2 metadata downloads to prevent backup hang (#162562) 2026-02-13 19:23:54 +00:00
hanwg
eb64b6bdee Fix config flow bug for Telegram bot (#162555) 2026-02-13 19:23:53 +00:00
Andrea Turri
ecb288b735 Add new Miele mappings (#162544) 2026-02-13 19:23:52 +00:00
Norbert Rittel
a419c9c420 Sentence-case "speech-to-text" in google_cloud (#162534) 2026-02-13 19:23:51 +00:00
Brett Adams
dd29133324 Fix Tesla Fleet partner registration to use all regions (#162525)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 19:23:50 +00:00
Allen Porter
90f22ea516 Bump grpc to 1.78.0 (#162520) 2026-02-13 19:23:48 +00:00
Peter Grauvogel
9db1428265 Fix Green Planet Energy price unit conversion (#162511) 2026-02-13 19:23:47 +00:00
Denis Shulyaka
a696b05b0d Fix JSON serialization of time objects in Cloud conversation tool results (#162506) 2026-02-13 19:23:46 +00:00
Denis Shulyaka
77ddb63b73 Fix JSON serialization of time objects in Open Router tool results (#162505) 2026-02-13 19:23:44 +00:00
Denis Shulyaka
4180a6e176 Fix JSON serialization of time objects in Ollama tool results (#162502)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 19:23:43 +00:00
Denis Shulyaka
6d74c912d2 Fix JSON serialization of datetime objects in Google Generative AI tool results (#162495) 2026-02-13 19:23:42 +00:00
Denis Shulyaka
8a01dfcc00 Fix JSON serialization of time objects in OpenAI tool results (#162490) 2026-02-13 19:23:40 +00:00
Brett Adams
9722898dc6 Fix device_class of backup reserve sensor in Tessie (#162459) 2026-02-13 19:23:39 +00:00
Brett Adams
7438c71fcb Fix device_class of backup reserve sensor in teslemetry (#162458) 2026-02-13 19:23:38 +00:00
Christian Lackas
0b5e55b923 Fix absolute humidity sensor on HmIP-WGT glass thermostats (#162455) 2026-02-13 19:23:37 +00:00
ElCruncharino
61ed959e8e Fix AsyncIteratorReader blocking after stream exhaustion (#161731) 2026-02-13 19:17:20 +00:00
Jaap Pieroen
3989532465 Bump essent-dynamic-pricing to 0.3.1 (#160958)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-02-13 19:17:18 +00:00
Franck Nijhof
28027ddca4 2026.2.1 (#162450) 2026-02-06 22:44:07 +01:00
Franck Nijhof
fe0d7b3cca Bump version to 2026.2.1 2026-02-06 20:49:26 +00:00
jameson_uk
0dcc4e9527 dep: bump aioamazondevices to 11.1.3 (#162437) 2026-02-06 20:47:38 +00:00
Artur Pragacz
b13b189703 Make bad entity ID detection more lenient (#162425) 2026-02-06 20:47:37 +00:00
epenet
150829f599 Fix invalid yardian snaphots (#162422) 2026-02-06 20:47:36 +00:00
Joost Lekkerkerker
57dd9d9c23 Remove double unit of measurement for yardian (#162412) 2026-02-06 20:47:34 +00:00
Sab44
e2056cb12c Bump librehardwaremonitor-api to version 1.9.1 (#162409) 2026-02-06 20:47:33 +00:00
Joost Lekkerkerker
fa2c8992cf Remove entity id overwrite for ambient station (#162403) 2026-02-06 20:47:32 +00:00
Matt Zimmerman
ddf5c7fe3a Add missing config flow strings to SmartTub (#162375)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 20:47:31 +00:00
Matt Zimmerman
7034ed6d3f Bump python-smarttub to 0.0.47 (#162367)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 20:47:29 +00:00
Aaron Godfrey
9015b53c1b Fix conversion of data for todo.* actions (#162366) 2026-02-06 20:47:28 +00:00
Jordan Harvey
1cfa6561f7 Update pynintendoparental requirement to version 2.3.2.1 (#162362) 2026-02-06 20:47:27 +00:00
Shay Levy
eead02dcca Fix Shelly Linkedgo Thermostat status update (#162339) 2026-02-06 20:47:26 +00:00
Arie Catsman
456e51a221 Bump pyenphase to 2.4.5 (#162324) 2026-02-06 20:47:25 +00:00
Luo Chen
5d984ce186 Fix unicode escaping in MCP server tool response (#162319)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-02-06 20:47:24 +00:00
Oliver
61f45489ac Add mapping for stopped state to denonavr media player (#162283) 2026-02-06 20:47:23 +00:00
Tomás Correia
f72c643b38 Fix multipart upload to use consistent part sizes for R2/S3 (#162278) 2026-02-06 20:47:22 +00:00
Oliver
27bc26e886 Bump denonavr to 1.3.2 (#162271) 2026-02-06 20:47:20 +00:00
Thomas55555
0e9f03cbc1 Bump google_air_quality_api to 3.0.1 (#162233) 2026-02-06 20:47:19 +00:00
David Bonnes
9480c33fb0 Bump evohome-async to 1.1.3 (#162232) 2026-02-06 20:47:18 +00:00
Jonathan
3e6b8663e8 Fix device_class of backup reserve sensor (#161178) 2026-02-06 20:47:17 +00:00
epenet
1c69a83793 Fix redundant off preset in Tuya climate (#161040) 2026-02-06 20:47:16 +00:00
26 changed files with 356 additions and 179 deletions

2
CODEOWNERS generated
View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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:

View File

@@ -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%]"
}
}
}

View File

@@ -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()

View File

@@ -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"]
}

View File

@@ -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
}

View File

@@ -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: |

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -1895,7 +1895,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
"spc",
"speedtestdotnet",
"spider",
"splunk",
"spotify",
"sql",
"srp_energy",

View File

@@ -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"

View File

@@ -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,
)

View File

@@ -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"

View File

@@ -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,

View File

@@ -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

View File

@@ -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',
}),
})

View File

@@ -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>,
}),

View File

@@ -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

View File

@@ -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(