mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 09:23:17 -04:00
Fix delayed Duco bypass target updates (#181367)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Number platform for the Duco integration."""
|
||||
|
||||
from dataclasses import replace
|
||||
import logging
|
||||
from typing import override
|
||||
|
||||
@@ -129,7 +130,7 @@ class DucoBypassSupplyTemperatureTargetNumber(DucoEntity, NumberEntity):
|
||||
try:
|
||||
if self.unit_of_measurement != self.native_unit_of_measurement:
|
||||
value = target.normalize_value(value)
|
||||
await self.coordinator.client.async_set_bypass_supply_temperature_target(
|
||||
updated_target = await self.coordinator.client.async_set_bypass_supply_temperature_target(
|
||||
self._zone_id, value, target=target
|
||||
)
|
||||
except ValueError as err:
|
||||
@@ -157,4 +158,14 @@ class DucoBypassSupplyTemperatureTargetNumber(DucoEntity, NumberEntity):
|
||||
translation_key="failed_to_set_bypass_supply_temperature_target",
|
||||
) from err
|
||||
|
||||
await self.coordinator.async_request_refresh()
|
||||
# Do not let a completed write mask a concurrent coordinator refresh failure.
|
||||
if self.coordinator.last_update_success:
|
||||
self.coordinator.async_set_updated_data(
|
||||
replace(
|
||||
self.coordinator.data,
|
||||
bypass_supply_temperature_targets={
|
||||
**self.coordinator.data.bypass_supply_temperature_targets,
|
||||
self._zone_id: updated_target,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -282,11 +282,11 @@ def mock_duco_client(
|
||||
temperature: float,
|
||||
*,
|
||||
target: BypassSupplyTemperatureTarget,
|
||||
) -> None:
|
||||
) -> BypassSupplyTemperatureTarget:
|
||||
target.validate_value(temperature)
|
||||
mock_bypass_supply_temperature_targets[zone_id] = replace(
|
||||
target, value=temperature
|
||||
)
|
||||
updated_target = replace(target, zone_id=zone_id, value=temperature)
|
||||
mock_bypass_supply_temperature_targets[zone_id] = updated_target
|
||||
return updated_target
|
||||
|
||||
with (
|
||||
patch(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Tests for the Duco number platform."""
|
||||
|
||||
import asyncio
|
||||
from dataclasses import replace
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, call
|
||||
|
||||
from duco_connectivity import (
|
||||
BypassSupplyTemperatureTarget,
|
||||
@@ -66,6 +67,61 @@ async def test_bypass_supply_temperature_target_numbers_support_all_exposed_zone
|
||||
mock_duco_client.async_get_bypass_supply_temperature_targets.assert_awaited_once_with()
|
||||
|
||||
|
||||
async def test_successful_write_does_not_recover_failed_coordinator(
|
||||
hass: HomeAssistant,
|
||||
mock_bypass_supply_temperature_targets: dict[int, BypassSupplyTemperatureTarget],
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_duco_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test a successful write does not recover a failed coordinator."""
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.NUMBER])
|
||||
write_started = asyncio.Event()
|
||||
release_write = asyncio.Event()
|
||||
target = mock_bypass_supply_temperature_targets[1]
|
||||
|
||||
async def set_bypass_supply_temperature_target(
|
||||
zone_id: int,
|
||||
temperature: float,
|
||||
*,
|
||||
target: BypassSupplyTemperatureTarget,
|
||||
) -> BypassSupplyTemperatureTarget:
|
||||
updated_target = replace(target, zone_id=zone_id, value=temperature)
|
||||
mock_bypass_supply_temperature_targets[zone_id] = updated_target
|
||||
write_started.set()
|
||||
await release_write.wait()
|
||||
return updated_target
|
||||
|
||||
mock_duco_client.async_set_bypass_supply_temperature_target.side_effect = (
|
||||
set_bypass_supply_temperature_target
|
||||
)
|
||||
write_task = asyncio.create_task(
|
||||
hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: _ZONE_1_ENTITY_ID, "value": 20.5},
|
||||
blocking=True,
|
||||
)
|
||||
)
|
||||
await write_started.wait()
|
||||
|
||||
mock_duco_client.async_get_nodes.side_effect = DucoError("Temporary update failure")
|
||||
await mock_config_entry.runtime_data.async_refresh()
|
||||
|
||||
state = hass.states.get(_ZONE_1_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
release_write.set()
|
||||
await write_task
|
||||
|
||||
mock_duco_client.async_set_bypass_supply_temperature_target.assert_awaited_once_with(
|
||||
1, 20.5, target=target
|
||||
)
|
||||
state = hass.states.get(_ZONE_1_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||
async def test_bypass_supply_temperature_target_number_entities_state(
|
||||
hass: HomeAssistant,
|
||||
@@ -98,22 +154,30 @@ async def test_set_bypass_supply_temperature_target(
|
||||
mock_bypass_supply_temperature_targets: dict[int, BypassSupplyTemperatureTarget],
|
||||
mock_duco_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test setting a bypass target refreshes the number from the box."""
|
||||
"""Test consecutive bypass target writes update directly from their responses."""
|
||||
target = mock_bypass_supply_temperature_targets[1]
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: _ZONE_1_ENTITY_ID, "value": 20.5},
|
||||
blocking=True,
|
||||
)
|
||||
for value in (20.5, 21.0):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: _ZONE_1_ENTITY_ID, "value": value},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_duco_client.async_set_bypass_supply_temperature_target.assert_awaited_once_with(
|
||||
1, 20.5, target=target
|
||||
state = hass.states.get(_ZONE_1_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == str(value)
|
||||
|
||||
assert (
|
||||
mock_duco_client.async_set_bypass_supply_temperature_target.await_args_list
|
||||
== [
|
||||
call(1, 20.5, target=target),
|
||||
call(1, 21.0, target=replace(target, value=20.5)),
|
||||
]
|
||||
)
|
||||
state = hass.states.get(_ZONE_1_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == "20.5"
|
||||
mock_duco_client.async_get_bypass_supply_temperature_targets.assert_awaited_once_with()
|
||||
assert mock_bypass_supply_temperature_targets[1] == replace(target, value=21.0)
|
||||
|
||||
|
||||
async def test_set_bypass_supply_temperature_target_honors_increment_metadata(
|
||||
|
||||
Reference in New Issue
Block a user