Compare commits

...

9 Commits

Author SHA1 Message Date
dependabot[bot] f80c71d524 Bump github/gh-aw-actions from 0.74.9 to 0.75.0
Bumps [github/gh-aw-actions](https://github.com/github/gh-aw-actions) from 0.74.9 to 0.75.0.
- [Release notes](https://github.com/github/gh-aw-actions/releases)
- [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/gh-aw-actions/compare/318d7f4901f78b85e25b91709cf0109ac9b425f6...f889c9c3c06adeaabccefc06e29c42733ee05dff)

---
updated-dependencies:
- dependency-name: github/gh-aw-actions
  dependency-version: 0.75.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 06:05:37 +00:00
Brett Adams 5681ba40f1 Move Teslemetry destination name from device tracker to a sensor (#172514)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 07:56:32 +02:00
Brett Adams 8a9a1c5fed Move Tesla Fleet route destination from device tracker to a sensor (#172513)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 07:55:44 +02:00
Franck Nijhof c587e101af Reduce Wyoming satellite disconnect log to debug level (#172499) 2026-05-28 19:18:14 -05:00
Franck Nijhof 6eeeac46f3 Convert Roomba hw_version to string for device registry (#172497) 2026-05-28 23:13:08 +02:00
Franck Nijhof 86542b8ad0 Increase ConfigEntryNotReady retry backoff cap from 80s to 10 minutes (#172487) 2026-05-28 22:41:54 +02:00
Franck Nijhof 7e07e7062c Add prog operating mode to Overkiz Atlantic heater HVAC mapping (#172491) 2026-05-28 22:21:53 +02:00
Franck Nijhof d7c13fee27 Fix Tado config flow crash on device activation polling (#172486) 2026-05-28 22:06:24 +02:00
Ronald van der Meer a0a44f7a25 Refactor Duco tests to use shared fixtures (#172351) 2026-05-28 22:04:25 +02:00
22 changed files with 389 additions and 178 deletions
+7 -7
View File
@@ -36,7 +36,7 @@
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
# - github/gh-aw-actions/setup@318d7f4901f78b85e25b91709cf0109ac9b425f6 # v0.74.9
# - github/gh-aw-actions/setup@f889c9c3c06adeaabccefc06e29c42733ee05dff # v0.75.0
#
# Container images used:
# - ghcr.io/github/gh-aw-firewall/agent:0.25.46
@@ -90,7 +90,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@318d7f4901f78b85e25b91709cf0109ac9b425f6 # v0.74.9
uses: github/gh-aw-actions/setup@f889c9c3c06adeaabccefc06e29c42733ee05dff # v0.75.0
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -352,7 +352,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@318d7f4901f78b85e25b91709cf0109ac9b425f6 # v0.74.9
uses: github/gh-aw-actions/setup@f889c9c3c06adeaabccefc06e29c42733ee05dff # v0.75.0
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -961,7 +961,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@318d7f4901f78b85e25b91709cf0109ac9b425f6 # v0.74.9
uses: github/gh-aw-actions/setup@f889c9c3c06adeaabccefc06e29c42733ee05dff # v0.75.0
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1100,7 +1100,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@318d7f4901f78b85e25b91709cf0109ac9b425f6 # v0.74.9
uses: github/gh-aw-actions/setup@f889c9c3c06adeaabccefc06e29c42733ee05dff # v0.75.0
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1325,7 +1325,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@318d7f4901f78b85e25b91709cf0109ac9b425f6 # v0.74.9
uses: github/gh-aw-actions/setup@f889c9c3c06adeaabccefc06e29c42733ee05dff # v0.75.0
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1383,7 +1383,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@318d7f4901f78b85e25b91709cf0109ac9b425f6 # v0.74.9
uses: github/gh-aw-actions/setup@f889c9c3c06adeaabccefc06e29c42733ee05dff # v0.75.0
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -57,6 +57,7 @@ OVERKIZ_TO_HVAC_MODE: dict[str, HVACMode] = {
OverkizCommandParam.STANDBY: HVACMode.OFF, # main command
OverkizCommandParam.AUTO: HVACMode.AUTO,
OverkizCommandParam.EXTERNAL: HVACMode.AUTO,
OverkizCommandParam.PROG: HVACMode.AUTO,
OverkizCommandParam.INTERNAL: HVACMode.AUTO, # main command
}
+5 -1
View File
@@ -29,7 +29,11 @@ class IRobotEntity(Entity):
model=self.vacuum_state.get("sku"),
name=str(self.vacuum_state.get("name")),
sw_version=self.vacuum_state.get("softwareVer"),
hw_version=self.vacuum_state.get("hardwareRev"),
hw_version=(
str(hw_rev)
if (hw_rev := self.vacuum_state.get("hardwareRev")) is not None
else None
),
)
if mac_address := self.vacuum_state.get("hwPartsRev", {}).get(
+6 -4
View File
@@ -40,6 +40,8 @@ class TadoConfigFlow(ConfigFlow, domain=DOMAIN):
login_task: asyncio.Task | None = None
refresh_token: str | None = None
tado: Tado | None = None
tado_device_url: str = ""
user_code: str = ""
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
@@ -69,8 +71,8 @@ class TadoConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.exception("Error while initiating Tado")
return self.async_abort(reason="cannot_connect")
assert self.tado is not None
tado_device_url = self.tado.device_verification_url()
user_code = URL(tado_device_url).query["user_code"]
self.tado_device_url = self.tado.device_verification_url()
self.user_code = URL(self.tado_device_url).query["user_code"]
async def _wait_for_login() -> None:
"""Wait for the user to login."""
@@ -119,8 +121,8 @@ class TadoConfigFlow(ConfigFlow, domain=DOMAIN):
step_id="user",
progress_action="wait_for_device",
description_placeholders={
"url": tado_device_url,
"code": user_code,
"url": self.tado_device_url,
"code": self.user_code,
},
progress_task=self.login_task,
)
@@ -2,7 +2,6 @@
from homeassistant.components.device_tracker import TrackerEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_HOME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
@@ -83,11 +82,3 @@ class TeslaFleetDeviceTrackerRouteEntity(TeslaFleetDeviceTrackerEntity):
self.get("drive_state_active_route_longitude", False) is None
or self.get("drive_state_active_route_latitude", False) is None
)
@property
def location_name(self) -> str | None:
"""Return a location name for the current location of the device."""
location = self.get("drive_state_active_route_destination")
if location == "Home":
return STATE_HOME
return location
@@ -280,6 +280,10 @@ VEHICLE_DESCRIPTIONS: tuple[TeslaFleetSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfLength.MILES,
device_class=SensorDeviceClass.DISTANCE,
),
TeslaFleetSensorEntityDescription(
key="drive_state_active_route_destination",
entity_registry_enabled_default=False,
),
)
@@ -11,7 +11,6 @@ from homeassistant.components.device_tracker import (
TrackerEntity,
TrackerEntityDescription,
)
from homeassistant.const import STATE_HOME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
@@ -31,12 +30,6 @@ class TeslemetryDeviceTrackerEntityDescription(TrackerEntityDescription):
[TeslemetryStreamVehicle, Callable[[TeslaLocation | None], None]],
Callable[[], None],
]
name_listener: (
Callable[
[TeslemetryStreamVehicle, Callable[[str | None], None]], Callable[[], None]
]
| None
) = None
streaming_firmware: str
polling_prefix: str | None = None
@@ -54,9 +47,6 @@ DESCRIPTIONS: tuple[TeslemetryDeviceTrackerEntityDescription, ...] = (
value_listener=lambda vehicle, callback: vehicle.listen_DestinationLocation(
callback
),
name_listener=lambda vehicle, callback: vehicle.listen_DestinationName(
callback
),
streaming_firmware="2024.26",
),
TeslemetryDeviceTrackerEntityDescription(
@@ -126,11 +116,6 @@ class TeslemetryVehiclePollingDeviceTrackerEntity(
self._attr_longitude = self.get(
f"{self.entity_description.polling_prefix}_longitude"
)
self._attr_location_name = self.get(
f"{self.entity_description.polling_prefix}_destination"
)
if self._attr_location_name == "Home":
self._attr_location_name = STATE_HOME
self._attr_available = (
self._attr_latitude is not None and self._attr_longitude is not None
)
@@ -158,28 +143,14 @@ class TeslemetryStreamingDeviceTrackerEntity(
if (state := await self.async_get_last_state()) is not None:
self._attr_latitude = state.attributes.get("latitude")
self._attr_longitude = state.attributes.get("longitude")
self._attr_location_name = state.attributes.get("location_name")
self.async_on_remove(
self.entity_description.value_listener(
self.vehicle.stream_vehicle, self._location_callback
)
)
if self.entity_description.name_listener:
self.async_on_remove(
self.entity_description.name_listener(
self.vehicle.stream_vehicle, self._name_callback
)
)
def _location_callback(self, location: TeslaLocation | None) -> None:
"""Update the value of the entity."""
self._attr_latitude = None if location is None else location.latitude
self._attr_longitude = None if location is None else location.longitude
self.async_write_ha_state()
def _name_callback(self, name: str | None) -> None:
"""Update the value of the entity."""
self._attr_location_name = name
if self._attr_location_name == "Home":
self._attr_location_name = STATE_HOME
self.async_write_ha_state()
@@ -510,6 +510,14 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = (
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
TeslemetryVehicleSensorEntityDescription(
key="drive_state_active_route_destination",
polling=True,
streaming_listener=lambda vehicle, callback: vehicle.listen_DestinationName(
callback
),
entity_registry_enabled_default=False,
),
TeslemetryVehicleSensorEntityDescription(
key="drive_state_active_route_traffic_minutes_delay",
polling=True,
@@ -468,7 +468,7 @@ class WyomingAssistSatellite(WyomingSatelliteEntity, AssistSatelliteEntity):
async def on_restart(self) -> None:
"""Block until pipeline loop will be restarted."""
_LOGGER.warning(
_LOGGER.debug(
"Satellite has been disconnected. Reconnecting in %s second(s)",
_RECONNECT_SECONDS,
)
+3 -1
View File
@@ -138,6 +138,8 @@ SAVE_DELAY = 1
DISCOVERY_COOLDOWN = 1
SETUP_RETRY_MAX_WAIT = 600 # 10 minutes
ISSUE_UNIQUE_ID_COLLISION = "config_entry_unique_id_collision"
UNIQUE_ID_COLLISION_TITLE_LIMIT = 5
@@ -836,7 +838,7 @@ class ConfigEntry[_DataT = Any]:
error_reason_translation_key,
error_reason_translation_placeholders,
)
wait_time = 2 ** min(self._tries, 4) * 5 + (
wait_time = min(2**self._tries * 5, SETUP_RETRY_MAX_WAIT) + (
randint(RANDOM_MICROSECOND_MIN, RANDOM_MICROSECOND_MAX) / 1000000
)
self._tries += 1
+32
View File
@@ -1 +1,33 @@
"""Tests for the Duco integration."""
from collections.abc import Sequence
from unittest.mock import patch
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_integration(
hass: HomeAssistant,
config_entry: MockConfigEntry,
) -> MockConfigEntry:
"""Set up the full Duco integration for testing."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry
async def setup_platform_integration(
hass: HomeAssistant,
config_entry: MockConfigEntry,
platforms: Sequence[Platform],
) -> MockConfigEntry:
"""Set up selected Duco platforms for testing."""
config_entry.add_to_hass(hass)
with patch("homeassistant.components.duco.PLATFORMS", list(platforms)):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry
+4 -110
View File
@@ -23,6 +23,8 @@ from homeassistant.components.duco.const import DOMAIN
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry, load_json_array_fixture
TEST_HOST = "192.168.1.100"
@@ -159,112 +161,7 @@ def mock_lan_info() -> LanInfo:
@pytest.fixture
def mock_nodes() -> list[Node]:
"""Return a list of nodes covering all supported types."""
return [
Node(
node_id=1,
general=NodeGeneralInfo(
node_type="BOX",
sub_type=1,
network_type="VIRT",
parent=0,
asso=0,
name="Living",
identify=0,
),
ventilation=NodeVentilationInfo(
state="AUTO",
time_state_remain=0,
time_state_end=0,
mode="AUTO",
flow_lvl_tgt=0,
),
sensor=NodeSensorInfo(
co2=None,
iaq_co2=None,
rh=None,
iaq_rh=None,
temp=27.9,
),
),
Node(
node_id=2,
general=NodeGeneralInfo(
node_type="UCCO2",
sub_type=0,
network_type="RF",
parent=1,
asso=1,
name="Office CO2",
identify=0,
),
ventilation=NodeVentilationInfo(
state="AUTO",
time_state_remain=0,
time_state_end=0,
mode="-",
flow_lvl_tgt=None,
),
sensor=NodeSensorInfo(
co2=405,
iaq_co2=80,
rh=None,
iaq_rh=None,
temp=19.8,
),
),
Node(
node_id=113,
general=NodeGeneralInfo(
node_type="BSRH",
sub_type=0,
network_type="RF",
parent=1,
asso=1,
name="Bathroom RH",
identify=0,
),
ventilation=NodeVentilationInfo(
state="AUTO",
time_state_remain=0,
time_state_end=0,
mode="-",
flow_lvl_tgt=None,
),
sensor=NodeSensorInfo(
co2=None,
iaq_co2=None,
rh=42.0,
iaq_rh=85,
temp=27.9,
),
),
Node(
node_id=50,
general=NodeGeneralInfo(
node_type="UCRH",
sub_type=0,
network_type="RF",
parent=1,
asso=1,
name="Kitchen RH",
identify=0,
),
ventilation=NodeVentilationInfo(
state="AUTO",
time_state_remain=0,
time_state_end=0,
mode="-",
flow_lvl_tgt=None,
),
sensor=NodeSensorInfo(
co2=None,
iaq_co2=None,
rh=61.0,
iaq_rh=90,
temp=22.5,
),
),
]
return load_nodes_fixture("nodes.json")
@pytest.fixture
@@ -327,7 +224,4 @@ async def init_integration(
mock_duco_client: AsyncMock,
) -> MockConfigEntry:
"""Set up the Duco integration for testing."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry
return await setup_integration(hass, mock_config_entry)
+106
View File
@@ -0,0 +1,106 @@
[
{
"node_id": 1,
"general": {
"node_type": "BOX",
"sub_type": 1,
"network_type": "VIRT",
"parent": 0,
"asso": 0,
"name": "Living",
"identify": 0
},
"ventilation": {
"state": "AUTO",
"time_state_remain": 0,
"time_state_end": 0,
"mode": "AUTO",
"flow_lvl_tgt": 0
},
"sensor": {
"co2": null,
"iaq_co2": null,
"rh": null,
"iaq_rh": null,
"temp": 27.9
}
},
{
"node_id": 2,
"general": {
"node_type": "UCCO2",
"sub_type": 0,
"network_type": "RF",
"parent": 1,
"asso": 1,
"name": "Office CO2",
"identify": 0
},
"ventilation": {
"state": "AUTO",
"time_state_remain": 0,
"time_state_end": 0,
"mode": "-",
"flow_lvl_tgt": null
},
"sensor": {
"co2": 405,
"iaq_co2": 80,
"rh": null,
"iaq_rh": null,
"temp": 19.8
}
},
{
"node_id": 113,
"general": {
"node_type": "BSRH",
"sub_type": 0,
"network_type": "RF",
"parent": 1,
"asso": 1,
"name": "Bathroom RH",
"identify": 0
},
"ventilation": {
"state": "AUTO",
"time_state_remain": 0,
"time_state_end": 0,
"mode": "-",
"flow_lvl_tgt": null
},
"sensor": {
"co2": null,
"iaq_co2": null,
"rh": 42.0,
"iaq_rh": 85,
"temp": 27.9
}
},
{
"node_id": 50,
"general": {
"node_type": "UCRH",
"sub_type": 0,
"network_type": "RF",
"parent": 1,
"asso": 1,
"name": "Kitchen RH",
"identify": 0
},
"ventilation": {
"state": "AUTO",
"time_state_remain": 0,
"time_state_end": 0,
"mode": "-",
"flow_lvl_tgt": null
},
"sensor": {
"co2": null,
"iaq_co2": null,
"rh": 61.0,
"iaq_rh": 90,
"temp": 22.5
}
}
]
+4 -6
View File
@@ -1,7 +1,7 @@
"""Tests for the Duco fan platform."""
import logging
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock
from duco_connectivity import DucoConnectionError, DucoError, DucoRateLimitError
from freezegun.api import FrozenDateTimeFactory
@@ -21,6 +21,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from . import setup_platform_integration
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
_FAN_ENTITY = "fan.living"
@@ -33,11 +35,7 @@ async def init_integration(
mock_duco_client: AsyncMock,
) -> MockConfigEntry:
"""Set up only the fan platform for testing."""
mock_config_entry.add_to_hass(hass)
with patch("homeassistant.components.duco.PLATFORMS", [Platform.FAN]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry
return await setup_platform_integration(hass, mock_config_entry, [Platform.FAN])
@pytest.mark.usefixtures("init_integration")
+4 -6
View File
@@ -1,7 +1,7 @@
"""Tests for the Duco sensor platform."""
import logging
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock
from duco_connectivity import (
DucoConnectionError,
@@ -22,6 +22,8 @@ from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import setup_platform_integration
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
@@ -34,11 +36,7 @@ async def init_integration(
) -> MockConfigEntry:
"""Set up only the sensor platform for testing."""
mock_duco_client.async_get_nodes.return_value = mock_sensor_nodes
mock_config_entry.add_to_hass(hass)
with patch("homeassistant.components.duco.PLATFORMS", [Platform.SENSOR]):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry
return await setup_platform_integration(hass, mock_config_entry, [Platform.SENSOR])
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
+37
View File
@@ -231,6 +231,43 @@ async def test_options_flow(
assert result["data"] == {CONF_FALLBACK: CONST_OVERLAY_TADO_DEFAULT}
async def test_show_progress_polling(
hass: HomeAssistant,
mock_tado_api: MagicMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test progress step re-entry while login task is still running."""
event = threading.Event()
def mock_tado_api_device_activation() -> None:
event.wait(timeout=5)
mock_tado_api.device_activation = mock_tado_api_device_activation
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "user"
assert result["description_placeholders"]["url"] is not None
assert result["description_placeholders"]["code"] == "TEST"
# Poll again while task is still running — this re-enters async_step_user
# with self.tado already set
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["description_placeholders"]["url"] is not None
assert result["description_placeholders"]["code"] == "TEST"
# Now complete the login
event.set()
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.CREATE_ENTRY
async def test_homekit(hass: HomeAssistant, mock_tado_api: MagicMock) -> None:
"""Test that we abort from homekit if tado is already setup."""
@@ -108,6 +108,6 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'home',
'state': 'not_home',
})
# ---
@@ -3216,6 +3216,69 @@
'state': 'stopped',
})
# ---
# name: test_sensors[sensor.test_destination-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.test_destination',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Destination',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Destination',
'platform': 'tesla_fleet',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'drive_state_active_route_destination',
'unique_id': 'LRWXF7EK4KC700000-drive_state_active_route_destination',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.test_destination-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Destination',
}),
'context': <ANY>,
'entity_id': 'sensor.test_destination',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'Home',
})
# ---
# name: test_sensors[sensor.test_destination-statealt]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Destination',
}),
'context': <ANY>,
'entity_id': 'sensor.test_destination',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_sensors[sensor.test_distance_to_arrival-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
@@ -108,7 +108,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'home',
'state': 'not_home',
})
# ---
# name: test_device_tracker_alt[device_tracker.test_location-statealt]
@@ -3228,6 +3228,69 @@
'state': 'stopped',
})
# ---
# name: test_sensors[sensor.test_destination-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.test_destination',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Destination',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Destination',
'platform': 'teslemetry',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'drive_state_active_route_destination',
'unique_id': 'LRW3F7EK4NC700000-drive_state_active_route_destination',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.test_destination-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Destination',
}),
'context': <ANY>,
'entity_id': 'sensor.test_destination',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'Home',
})
# ---
# name: test_sensors[sensor.test_destination-statealt]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Destination',
}),
'context': <ANY>,
'entity_id': 'sensor.test_destination',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---
# name: test_sensors[sensor.test_distance_to_arrival-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
@@ -81,7 +81,6 @@ async def test_device_tracker_streaming(
"latitude": 3.0,
"longitude": 4.0,
},
Signal.DESTINATION_NAME: "Home",
Signal.ORIGIN_LOCATION: None,
},
"createdAt": "2024-10-04T10:45:17.537Z",
@@ -91,7 +90,7 @@ async def test_device_tracker_streaming(
# Assert the entities have correct state values
assert hass.states.get("device_tracker.test_location").state == "not_home"
assert hass.states.get("device_tracker.test_route").state == "home"
assert hass.states.get("device_tracker.test_route").state == "not_home"
assert hass.states.get("device_tracker.test_origin").state == "unknown"
# Reload the entry
+38
View File
@@ -1702,6 +1702,44 @@ async def test_setup_raise_not_ready(
assert entry.reason is None
async def test_setup_not_ready_exponential_backoff(
hass: HomeAssistant,
manager: config_entries.ConfigEntries,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test setup retry uses exponential backoff capped at 10 minutes."""
entry = MockConfigEntry(domain="test")
entry.add_to_hass(hass)
attempts = 0
async def _mock_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
nonlocal attempts
attempts += 1
raise ConfigEntryNotReady
mock_integration(hass, MockModule("test", async_setup_entry=_mock_setup_entry))
mock_platform(hass, "test.config_flow", None)
await manager.async_setup(entry.entry_id)
assert attempts == 1
expected_waits = [5, 10, 20, 40, 80, 160, 320, 600, 600]
for i, wait in enumerate(expected_waits):
# Advance to just before the retry should fire
freezer.tick(wait - 1)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert attempts == i + 1, f"Retry {i + 1} fired too early"
# Advance past the retry point (+ 1s for jitter)
freezer.tick(2)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert attempts == i + 2, f"Retry {i + 1} did not fire"
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
async def test_setup_raise_not_ready_from_exception(
hass: HomeAssistant,
manager: config_entries.ConfigEntries,