This commit is contained in:
Franck Nijhof 2024-12-20 11:45:57 +01:00 committed by GitHub
commit 6974f61703
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 151 additions and 49 deletions

View File

@ -4,8 +4,6 @@ from __future__ import annotations
from typing import Any from typing import Any
from fjaraskupan import COMMAND_LIGHT_ON_OFF
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -62,7 +60,6 @@ class Light(CoordinatorEntity[FjaraskupanCoordinator], LightEntity):
if self.is_on: if self.is_on:
async with self.coordinator.async_connect_and_update() as device: async with self.coordinator.async_connect_and_update() as device:
await device.send_dim(0) await device.send_dim(0)
await device.send_command(COMMAND_LIGHT_ON_OFF)
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:

View File

@ -14,5 +14,5 @@
"documentation": "https://www.home-assistant.io/integrations/fjaraskupan", "documentation": "https://www.home-assistant.io/integrations/fjaraskupan",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["bleak", "fjaraskupan"], "loggers": ["bleak", "fjaraskupan"],
"requirements": ["fjaraskupan==2.3.0"] "requirements": ["fjaraskupan==2.3.2"]
} }

View File

@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/freebox", "documentation": "https://www.home-assistant.io/integrations/freebox",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["freebox_api"], "loggers": ["freebox_api"],
"requirements": ["freebox-api==1.1.0"], "requirements": ["freebox-api==1.2.1"],
"zeroconf": ["_fbx-api._tcp.local."] "zeroconf": ["_fbx-api._tcp.local."]
} }

View File

@ -14,5 +14,5 @@
"documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth", "documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["bleak", "bleak_esphome", "gardena_bluetooth"], "loggers": ["bleak", "bleak_esphome", "gardena_bluetooth"],
"requirements": ["gardena-bluetooth==1.4.4"] "requirements": ["gardena-bluetooth==1.5.0"]
} }

View File

@ -576,7 +576,7 @@ class IntegrationSensor(RestoreSensor):
if ( if (
self._max_sub_interval is not None self._max_sub_interval is not None
and source_state is not None and source_state is not None
and (source_state_dec := _decimal_state(source_state.state)) and (source_state_dec := _decimal_state(source_state.state)) is not None
): ):
@callback @callback

View File

@ -661,7 +661,7 @@ class MQTT:
self.conf.get(CONF_PORT, DEFAULT_PORT), self.conf.get(CONF_PORT, DEFAULT_PORT),
self.conf.get(CONF_KEEPALIVE, DEFAULT_KEEPALIVE), self.conf.get(CONF_KEEPALIVE, DEFAULT_KEEPALIVE),
) )
except OSError as err: except (OSError, mqtt.WebsocketConnectionError) as err:
_LOGGER.error("Failed to connect to MQTT server due to exception: %s", err) _LOGGER.error("Failed to connect to MQTT server due to exception: %s", err)
self._async_connection_result(False) self._async_connection_result(False)
finally: finally:

View File

@ -566,17 +566,13 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
# shuffle and repeat are not (yet) supported for external sources # shuffle and repeat are not (yet) supported for external sources
self._attr_shuffle = None self._attr_shuffle = None
self._attr_repeat = None self._attr_repeat = None
if TYPE_CHECKING: self._attr_media_position = int(player.elapsed_time or 0)
assert player.elapsed_time is not None
self._attr_media_position = int(player.elapsed_time)
self._attr_media_position_updated_at = ( self._attr_media_position_updated_at = (
utc_from_timestamp(player.elapsed_time_last_updated) utc_from_timestamp(player.elapsed_time_last_updated)
if player.elapsed_time_last_updated if player.elapsed_time_last_updated
else None else None
) )
if TYPE_CHECKING: self._prev_time = player.elapsed_time or 0
assert player.elapsed_time is not None
self._prev_time = player.elapsed_time
return return
if queue is None: if queue is None:

View File

@ -15,8 +15,8 @@ CONF_REFRESH_TOKEN_CREATION_TIME = "refresh_token_creation_time"
REFRESH_TOKEN_EXPIRY_TIME = timedelta(days=30) REFRESH_TOKEN_EXPIRY_TIME = timedelta(days=30)
SUPPORTED_DEVICE_TYPES = { SUPPORTED_DEVICE_TYPES = {
Platform.LIGHT: ["WallStation"], Platform.LIGHT: ["WallStation", "WallStation_ESP32"],
Platform.SWITCH: ["WallStation"], Platform.SWITCH: ["WallStation", "WallStation_ESP32"],
} }
KNOWN_UNSUPPORTED_DEVICE_TYPES = { KNOWN_UNSUPPORTED_DEVICE_TYPES = {
Platform.LIGHT: ["Mms100"], Platform.LIGHT: ["Mms100"],

View File

@ -239,7 +239,6 @@ class NiceGOUpdateCoordinator(DataUpdateCoordinator[dict[str, NiceGODevice]]):
].type, # Device type is not sent in device state update, and it can't change, so we just reuse the existing one ].type, # Device type is not sent in device state update, and it can't change, so we just reuse the existing one
BarrierState( BarrierState(
deviceId=raw_data["deviceId"], deviceId=raw_data["deviceId"],
desired=json.loads(raw_data["desired"]),
reported=json.loads(raw_data["reported"]), reported=json.loads(raw_data["reported"]),
connectionState=ConnectionState( connectionState=ConnectionState(
connected=raw_data["connectionState"]["connected"], connected=raw_data["connectionState"]["connected"],

View File

@ -21,6 +21,7 @@ from .entity import NiceGOEntity
DEVICE_CLASSES = { DEVICE_CLASSES = {
"WallStation": CoverDeviceClass.GARAGE, "WallStation": CoverDeviceClass.GARAGE,
"Mms100": CoverDeviceClass.GATE, "Mms100": CoverDeviceClass.GATE,
"WallStation_ESP32": CoverDeviceClass.GARAGE,
} }
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1

View File

@ -7,5 +7,5 @@
"integration_type": "hub", "integration_type": "hub",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["nice_go"], "loggers": ["nice_go"],
"requirements": ["nice-go==0.3.10"] "requirements": ["nice-go==1.0.0"]
} }

View File

@ -76,7 +76,7 @@ class OverkizConfigFlow(ConfigFlow, domain=DOMAIN):
for gateway in gateways: for gateway in gateways:
if is_overkiz_gateway(gateway.id): if is_overkiz_gateway(gateway.id):
gateway_id = gateway.id gateway_id = gateway.id
await self.async_set_unique_id(gateway_id) await self.async_set_unique_id(gateway_id, raise_on_progress=False)
return user_input return user_input

View File

@ -20,7 +20,7 @@
"integration_type": "hub", "integration_type": "hub",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"], "loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
"requirements": ["pyoverkiz==1.15.0"], "requirements": ["pyoverkiz==1.15.3"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_kizbox._tcp.local.", "type": "_kizbox._tcp.local.",

View File

@ -7,7 +7,7 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["roborock"], "loggers": ["roborock"],
"requirements": [ "requirements": [
"python-roborock==2.7.2", "python-roborock==2.8.1",
"vacuum-map-parser-roborock==0.1.2" "vacuum-map-parser-roborock==0.1.2"
] ]
} }

View File

@ -4,6 +4,7 @@ import logging
from typing import Any from typing import Any
from screenlogicpy import ScreenLogicError, ScreenLogicGateway from screenlogicpy import ScreenLogicError, ScreenLogicGateway
from screenlogicpy.const.common import ScreenLogicConnectionError
from screenlogicpy.const.data import SHARED_VALUES from screenlogicpy.const.data import SHARED_VALUES
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -64,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ScreenLogicConfigEntry)
try: try:
await gateway.async_connect(**connect_info) await gateway.async_connect(**connect_info)
await gateway.async_update() await gateway.async_update()
except ScreenLogicError as ex: except (ScreenLogicConnectionError, ScreenLogicError) as ex:
raise ConfigEntryNotReady(ex.msg) from ex raise ConfigEntryNotReady(ex.msg) from ex
coordinator = ScreenlogicDataUpdateCoordinator( coordinator = ScreenlogicDataUpdateCoordinator(

View File

@ -45,7 +45,9 @@ class TwinklyConfigFlow(ConfigFlow, domain=DOMAIN):
except (TimeoutError, ClientError): except (TimeoutError, ClientError):
errors[CONF_HOST] = "cannot_connect" errors[CONF_HOST] = "cannot_connect"
else: else:
await self.async_set_unique_id(device_info[DEV_ID]) await self.async_set_unique_id(
device_info[DEV_ID], raise_on_progress=False
)
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
return self._create_entry_from_device(device_info, host) return self._create_entry_from_device(device_info, host)

View File

@ -25,7 +25,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant" APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2024 MAJOR_VERSION: Final = 2024
MINOR_VERSION: Final = 12 MINOR_VERSION: Final = 12
PATCH_VERSION: Final = "4" PATCH_VERSION: Final = "5"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)

View File

@ -5,7 +5,7 @@ aiodiscover==2.1.0
aiodns==3.2.0 aiodns==3.2.0
aiohasupervisor==0.2.1 aiohasupervisor==0.2.1
aiohttp-fast-zlib==0.2.0 aiohttp-fast-zlib==0.2.0
aiohttp==3.11.10 aiohttp==3.11.11
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
aiozoneinfo==0.2.1 aiozoneinfo==0.2.1
astral==2.2 astral==2.2

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "homeassistant" name = "homeassistant"
version = "2024.12.4" version = "2024.12.5"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3." description = "Open-source home automation platform running on Python 3."
readme = "README.rst" readme = "README.rst"
@ -29,7 +29,7 @@ dependencies = [
# change behavior based on presence of supervisor. Deprecated with #127228 # change behavior based on presence of supervisor. Deprecated with #127228
# Lib can be removed with 2025.11 # Lib can be removed with 2025.11
"aiohasupervisor==0.2.1", "aiohasupervisor==0.2.1",
"aiohttp==3.11.10", "aiohttp==3.11.11",
"aiohttp_cors==0.7.0", "aiohttp_cors==0.7.0",
"aiohttp-fast-zlib==0.2.0", "aiohttp-fast-zlib==0.2.0",
"aiozoneinfo==0.2.1", "aiozoneinfo==0.2.1",

View File

@ -5,7 +5,7 @@
# Home Assistant Core # Home Assistant Core
aiodns==3.2.0 aiodns==3.2.0
aiohasupervisor==0.2.1 aiohasupervisor==0.2.1
aiohttp==3.11.10 aiohttp==3.11.11
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
aiohttp-fast-zlib==0.2.0 aiohttp-fast-zlib==0.2.0
aiozoneinfo==0.2.1 aiozoneinfo==0.2.1

View File

@ -912,7 +912,7 @@ fivem-api==0.1.2
fixerio==1.0.0a0 fixerio==1.0.0a0
# homeassistant.components.fjaraskupan # homeassistant.components.fjaraskupan
fjaraskupan==2.3.0 fjaraskupan==2.3.2
# homeassistant.components.flexit_bacnet # homeassistant.components.flexit_bacnet
flexit_bacnet==2.2.1 flexit_bacnet==2.2.1
@ -937,7 +937,7 @@ forecast-solar==4.0.0
fortiosapi==1.0.5 fortiosapi==1.0.5
# homeassistant.components.freebox # homeassistant.components.freebox
freebox-api==1.1.0 freebox-api==1.2.1
# homeassistant.components.free_mobile # homeassistant.components.free_mobile
freesms==0.2.0 freesms==0.2.0
@ -953,7 +953,7 @@ fyta_cli==0.7.0
gTTS==2.2.4 gTTS==2.2.4
# homeassistant.components.gardena_bluetooth # homeassistant.components.gardena_bluetooth
gardena-bluetooth==1.4.4 gardena-bluetooth==1.5.0
# homeassistant.components.google_assistant_sdk # homeassistant.components.google_assistant_sdk
gassist-text==0.0.11 gassist-text==0.0.11
@ -1460,7 +1460,7 @@ nextdns==4.0.0
nibe==2.13.0 nibe==2.13.0
# homeassistant.components.nice_go # homeassistant.components.nice_go
nice-go==0.3.10 nice-go==1.0.0
# homeassistant.components.niko_home_control # homeassistant.components.niko_home_control
niko-home-control==0.2.1 niko-home-control==0.2.1
@ -2149,7 +2149,7 @@ pyotgw==2.2.2
pyotp==2.8.0 pyotp==2.8.0
# homeassistant.components.overkiz # homeassistant.components.overkiz
pyoverkiz==1.15.0 pyoverkiz==1.15.3
# homeassistant.components.onewire # homeassistant.components.onewire
pyownet==0.10.0.post1 pyownet==0.10.0.post1
@ -2402,7 +2402,7 @@ python-rabbitair==0.0.8
python-ripple-api==0.0.3 python-ripple-api==0.0.3
# homeassistant.components.roborock # homeassistant.components.roborock
python-roborock==2.7.2 python-roborock==2.8.1
# homeassistant.components.smarttub # homeassistant.components.smarttub
python-smarttub==0.0.38 python-smarttub==0.0.38

View File

@ -771,7 +771,7 @@ fitbit==0.3.1
fivem-api==0.1.2 fivem-api==0.1.2
# homeassistant.components.fjaraskupan # homeassistant.components.fjaraskupan
fjaraskupan==2.3.0 fjaraskupan==2.3.2
# homeassistant.components.flexit_bacnet # homeassistant.components.flexit_bacnet
flexit_bacnet==2.2.1 flexit_bacnet==2.2.1
@ -793,7 +793,7 @@ foobot_async==1.0.0
forecast-solar==4.0.0 forecast-solar==4.0.0
# homeassistant.components.freebox # homeassistant.components.freebox
freebox-api==1.1.0 freebox-api==1.2.1
# homeassistant.components.fritz # homeassistant.components.fritz
# homeassistant.components.fritzbox_callmonitor # homeassistant.components.fritzbox_callmonitor
@ -806,7 +806,7 @@ fyta_cli==0.7.0
gTTS==2.2.4 gTTS==2.2.4
# homeassistant.components.gardena_bluetooth # homeassistant.components.gardena_bluetooth
gardena-bluetooth==1.4.4 gardena-bluetooth==1.5.0
# homeassistant.components.google_assistant_sdk # homeassistant.components.google_assistant_sdk
gassist-text==0.0.11 gassist-text==0.0.11
@ -1220,7 +1220,7 @@ nextdns==4.0.0
nibe==2.13.0 nibe==2.13.0
# homeassistant.components.nice_go # homeassistant.components.nice_go
nice-go==0.3.10 nice-go==1.0.0
# homeassistant.components.nfandroidtv # homeassistant.components.nfandroidtv
notifications-android-tv==0.1.5 notifications-android-tv==0.1.5
@ -1736,7 +1736,7 @@ pyotgw==2.2.2
pyotp==2.8.0 pyotp==2.8.0
# homeassistant.components.overkiz # homeassistant.components.overkiz
pyoverkiz==1.15.0 pyoverkiz==1.15.3
# homeassistant.components.onewire # homeassistant.components.onewire
pyownet==0.10.0.post1 pyownet==0.10.0.post1
@ -1923,7 +1923,7 @@ python-picnic-api==1.1.0
python-rabbitair==0.0.8 python-rabbitair==0.0.8
# homeassistant.components.roborock # homeassistant.components.roborock
python-roborock==2.7.2 python-roborock==2.8.1
# homeassistant.components.smarttub # homeassistant.components.smarttub
python-smarttub==0.0.38 python-smarttub==0.0.38

View File

@ -843,6 +843,39 @@ async def test_on_valid_source_expect_update_on_time(
assert float(state.state) < 1.8 assert float(state.state) < 1.8
async def test_on_0_source_expect_0_and_update_when_source_gets_positive(
hass: HomeAssistant,
) -> None:
"""Test whether time based integration updates the integral on a valid zero source."""
start_time = dt_util.utcnow()
with freeze_time(start_time) as freezer:
await _setup_integral_sensor(hass, max_sub_interval=DEFAULT_MAX_SUB_INTERVAL)
await _update_source_sensor(hass, 0)
await hass.async_block_till_done()
# wait one minute and one second
freezer.tick(61)
async_fire_time_changed(hass, dt_util.now())
await hass.async_block_till_done()
state = hass.states.get("sensor.integration")
assert condition.async_numeric_state(hass, state) is True
assert float(state.state) == 0 # integral is 0 after integration of 0
# wait one second and update state
freezer.tick(1)
async_fire_time_changed(hass, dt_util.now())
await _update_source_sensor(hass, 100)
await hass.async_block_till_done()
state = hass.states.get("sensor.integration")
# approx 100*1/3600 (right method after 1 second since last integration)
assert 0.027 < float(state.state) < 0.029
async def test_on_unvailable_source_expect_no_update_on_time( async def test_on_unvailable_source_expect_no_update_on_time(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:

View File

@ -1403,8 +1403,15 @@ async def test_handle_mqtt_timeout_on_callback(
assert not mock_debouncer.is_set() assert not mock_debouncer.is_set()
@pytest.mark.parametrize(
"exception",
[
OSError("Connection error"),
paho_mqtt.WebsocketConnectionError("Connection error"),
],
)
async def test_setup_raises_config_entry_not_ready_if_no_connect_broker( async def test_setup_raises_config_entry_not_ready_if_no_connect_broker(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture hass: HomeAssistant, caplog: pytest.LogCaptureFixture, exception: Exception
) -> None: ) -> None:
"""Test for setup failure if connection to broker is missing.""" """Test for setup failure if connection to broker is missing."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"})
@ -1413,7 +1420,7 @@ async def test_setup_raises_config_entry_not_ready_if_no_connect_broker(
with patch( with patch(
"homeassistant.components.mqtt.async_client.AsyncMQTTClient" "homeassistant.components.mqtt.async_client.AsyncMQTTClient"
) as mock_client: ) as mock_client:
mock_client().connect = MagicMock(side_effect=OSError("Connection error")) mock_client().connect = MagicMock(side_effect=exception)
assert await hass.config_entries.async_setup(entry.entry_id) assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert "Failed to connect to MQTT server due to exception:" in caplog.text assert "Failed to connect to MQTT server due to exception:" in caplog.text

View File

@ -20,7 +20,7 @@
"power", "power",
"enqueue" "enqueue"
], ],
"elapsed_time": 0, "elapsed_time": null,
"elapsed_time_last_updated": 0, "elapsed_time_last_updated": 0,
"state": "idle", "state": "idle",
"volume_level": 20, "volume_level": 20,

View File

@ -11,7 +11,6 @@
], ],
"state": { "state": {
"deviceId": "1", "deviceId": "1",
"desired": { "key": "value" },
"reported": { "reported": {
"displayName": "Test Garage 1", "displayName": "Test Garage 1",
"autoDisabled": false, "autoDisabled": false,
@ -42,7 +41,6 @@
], ],
"state": { "state": {
"deviceId": "2", "deviceId": "2",
"desired": { "key": "value" },
"reported": { "reported": {
"displayName": "Test Garage 2", "displayName": "Test Garage 2",
"autoDisabled": false, "autoDisabled": false,
@ -73,7 +71,6 @@
], ],
"state": { "state": {
"deviceId": "3", "deviceId": "3",
"desired": { "key": "value" },
"reported": { "reported": {
"displayName": "Test Garage 3", "displayName": "Test Garage 3",
"autoDisabled": false, "autoDisabled": false,
@ -101,7 +98,6 @@
], ],
"state": { "state": {
"deviceId": "4", "deviceId": "4",
"desired": { "key": "value" },
"reported": { "reported": {
"displayName": "Test Garage 4", "displayName": "Test Garage 4",
"autoDisabled": false, "autoDisabled": false,

View File

@ -81,7 +81,6 @@ async def test_firmware_update_required(
"displayName": "test-display-name", "displayName": "test-display-name",
"migrationStatus": "NOT_STARTED", "migrationStatus": "NOT_STARTED",
}, },
desired=None,
connectionState=None, connectionState=None,
version=None, version=None,
timestamp=None, timestamp=None,

View File

@ -4,12 +4,14 @@ from dataclasses import dataclass
from unittest.mock import DEFAULT, patch from unittest.mock import DEFAULT, patch
import pytest import pytest
from screenlogicpy import ScreenLogicGateway from screenlogicpy import ScreenLogicError, ScreenLogicGateway
from screenlogicpy.const.common import ScreenLogicConnectionError
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
from homeassistant.components.screenlogic import DOMAIN from homeassistant.components.screenlogic import DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util import slugify from homeassistant.util import slugify
@ -284,3 +286,35 @@ async def test_platform_setup(
for entity_id in tested_entity_ids: for entity_id in tested_entity_ids:
assert hass.states.get(entity_id) is not None assert hass.states.get(entity_id) is not None
@pytest.mark.parametrize(
"exception",
[ScreenLogicConnectionError, ScreenLogicError],
)
async def test_retry_on_connect_exception(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, exception: Exception
) -> None:
"""Test setup retries on expected exceptions."""
def stub_connect(*args, **kwargs):
raise exception
mock_config_entry.add_to_hass(hass)
with (
patch(
GATEWAY_DISCOVERY_IMPORT_PATH,
return_value={},
),
patch.multiple(
ScreenLogicGateway,
async_connect=stub_connect,
is_connected=False,
_async_connected_request=DEFAULT,
),
):
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY

View File

@ -5,6 +5,7 @@ from unittest.mock import patch
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import dhcp from homeassistant.components import dhcp
from homeassistant.components.twinkly.const import DOMAIN as TWINKLY_DOMAIN from homeassistant.components.twinkly.const import DOMAIN as TWINKLY_DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
@ -157,3 +158,39 @@ async def test_dhcp_already_exists(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_user_flow_works_discovery(hass: HomeAssistant) -> None:
"""Test user flow can continue after discovery happened."""
client = ClientMock()
with (
patch(
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
),
patch("homeassistant.components.twinkly.async_setup_entry", return_value=True),
):
await hass.config_entries.flow.async_init(
TWINKLY_DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
hostname="Twinkly_XYZ",
ip="1.2.3.4",
macaddress="aabbccddeeff",
),
)
result = await hass.config_entries.flow.async_init(
TWINKLY_DOMAIN,
context={"source": SOURCE_USER},
)
assert len(hass.config_entries.flow.async_progress(TWINKLY_DOMAIN)) == 2
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "10.0.0.131"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
# Verify the discovery flow was aborted
assert not hass.config_entries.flow.async_progress(TWINKLY_DOMAIN)