mirror of
https://github.com/home-assistant/core.git
synced 2026-09-27 01:46:11 -04:00
Refactor Duco polling to use the info overview (#182087)
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"""Data update coordinator for the Duco integration."""
|
||||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass, replace
|
||||
import logging
|
||||
from typing import cast, override
|
||||
@@ -73,6 +72,7 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
|
||||
config_entry=config_entry,
|
||||
name=DOMAIN,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
always_update=False,
|
||||
)
|
||||
self.client = client
|
||||
self._configured_node_names = {}
|
||||
@@ -219,53 +219,33 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
|
||||
exc_info=err,
|
||||
)
|
||||
|
||||
# LAN info only backs the diagnostic RSSI sensor, so failures on this
|
||||
# supplemental endpoint, including connection failures, should not make
|
||||
# the primary node entities unavailable.
|
||||
# The overview only backs supplemental entities, so failures preserve
|
||||
# known values where possible without making primary entities unavailable.
|
||||
rssi_wifi = self.data.rssi_wifi if self.data else None
|
||||
try:
|
||||
lan_info = await self.client.async_get_lan_info()
|
||||
except DucoError as err:
|
||||
_LOGGER.debug("Could not fetch Duco LAN info", exc_info=err)
|
||||
else:
|
||||
rssi_wifi = lan_info.rssi_wifi
|
||||
|
||||
# Diagnostics only back optional binary sensors. Preserve known components
|
||||
# but mark their data unavailable without failing the shared coordinator.
|
||||
diagnostics_were_available = (
|
||||
self.data is None or self.data.diagnostics_available
|
||||
)
|
||||
diagnostics_available = True
|
||||
diagnostics_available = False
|
||||
diagnostics_error: DucoError | None = None
|
||||
diagnostic_subsystems = self.data.diagnostic_subsystems if self.data else {}
|
||||
try:
|
||||
diagnostic_info = await self.client.async_get_diagnostics_info()
|
||||
except DucoError as err:
|
||||
diagnostics_available = False
|
||||
diagnostics_error = err
|
||||
else:
|
||||
diagnostic_subsystems = {
|
||||
diagnostic.component: diagnostic.status
|
||||
for diagnostic in diagnostic_info.diagnostic_subsystems
|
||||
}
|
||||
|
||||
# Heat recovery info only backs the optional filter timer sensor, so
|
||||
# failures on this supplemental endpoint should not make the primary
|
||||
# node entities unavailable. A None result leaves the sensor absent
|
||||
# but keeps the helper pollable so data can appear on a later refresh.
|
||||
time_filter_remain = None
|
||||
with suppress(DucoError):
|
||||
time_filter_remain = await self.client.async_get_time_filter_remaining()
|
||||
|
||||
ventilation_temperatures = (
|
||||
self.data.ventilation_temperatures if self.data else None
|
||||
)
|
||||
try:
|
||||
ventilation_temperatures = (
|
||||
await self.client.async_get_ventilation_temperature_info()
|
||||
)
|
||||
info_overview = await self.client.async_get_info_overview()
|
||||
except DucoError as err:
|
||||
_LOGGER.debug("Could not fetch Duco ventilation temperatures", exc_info=err)
|
||||
diagnostics_error = err
|
||||
_LOGGER.debug("Could not fetch Duco info overview", exc_info=err)
|
||||
else:
|
||||
rssi_wifi = info_overview.rssi_wifi
|
||||
diagnostics_available = True
|
||||
diagnostic_subsystems = {
|
||||
diagnostic.component: diagnostic.status
|
||||
for diagnostic in info_overview.diagnostic_subsystems
|
||||
}
|
||||
time_filter_remain = info_overview.time_filter_remain
|
||||
ventilation_temperatures = info_overview.ventilation_temperatures
|
||||
|
||||
bypass_supply_temperature_targets: dict[int, BypassSupplyTemperatureTarget] = {}
|
||||
try:
|
||||
|
||||
@@ -285,7 +285,7 @@ async def async_setup_entry(
|
||||
if node.node_id not in known_nodes:
|
||||
if node.general.node_type == NodeType.UNKNOWN:
|
||||
# Do not add the node to known_nodes so that it is re-evaluated
|
||||
# on every coordinator update. This allows entities to be
|
||||
# when coordinator data changes. This allows entities to be
|
||||
# created automatically once a firmware update or library
|
||||
# update adds support for the device type.
|
||||
_LOGGER.debug(
|
||||
|
||||
@@ -17,6 +17,7 @@ from duco_connectivity import (
|
||||
ConfigValueString,
|
||||
DiagComponent,
|
||||
DiagInfo,
|
||||
InfoOverview,
|
||||
KnownActionName,
|
||||
LanInfo,
|
||||
Node,
|
||||
@@ -315,6 +316,14 @@ def mock_duco_client(
|
||||
client.async_get_node_info.side_effect = get_node_info
|
||||
client.async_get_node_configs.return_value = node_configs_from_nodes(mock_nodes)
|
||||
client.async_get_node_actions.return_value = mock_node_actions
|
||||
client.async_get_info_overview.return_value = InfoOverview(
|
||||
rssi_wifi=mock_lan_info.rssi_wifi,
|
||||
diagnostic_subsystems=(
|
||||
DiagComponent(component="Ventilation", status="Ok"),
|
||||
),
|
||||
time_filter_remain=180,
|
||||
ventilation_temperatures=mock_ventilation_temperature_info,
|
||||
)
|
||||
client.async_get_time_filter_remaining.return_value = 180
|
||||
client.async_get_ventilation_temperature_info.return_value = (
|
||||
mock_ventilation_temperature_info
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
"""Tests for the Duco binary sensor platform."""
|
||||
|
||||
from dataclasses import replace
|
||||
import logging
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from duco_connectivity import (
|
||||
DiagComponent,
|
||||
DiagInfo,
|
||||
DucoConnectionError,
|
||||
DucoError,
|
||||
Node,
|
||||
)
|
||||
from duco_connectivity import DiagComponent, DucoConnectionError, DucoError, Node
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
@@ -45,13 +40,14 @@ async def test_diagnostic_binary_sensor_entity_registry_defaults(
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test the diagnostic binary sensor entity registry defaults."""
|
||||
mock_duco_client.async_get_diagnostics_info.return_value = DiagInfo(
|
||||
mock_duco_client.async_get_info_overview.return_value = replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
diagnostic_subsystems=(
|
||||
DiagComponent(component="Ventilation", status="Ok"),
|
||||
DiagComponent(component="Filter", status="Ok"),
|
||||
DiagComponent(component="VentCool", status="Ok"),
|
||||
DiagComponent(component="SunCtrl", status="Ok"),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.BINARY_SENSOR])
|
||||
@@ -80,8 +76,9 @@ async def test_unknown_diagnostic_subsystem_is_ignored(
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test an unknown diagnostic subsystem is not exposed."""
|
||||
mock_duco_client.async_get_diagnostics_info.return_value = DiagInfo(
|
||||
diagnostic_subsystems=(DiagComponent(component="Future Mode", status="Error"),)
|
||||
mock_duco_client.async_get_info_overview.return_value = replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
diagnostic_subsystems=(DiagComponent(component="Future Mode", status="Error"),),
|
||||
)
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.BINARY_SENSOR])
|
||||
@@ -106,10 +103,11 @@ async def test_diagnostic_binary_sensor_problem_state(
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test diagnostic statuses map to the expected problem state."""
|
||||
mock_duco_client.async_get_diagnostics_info.return_value = DiagInfo(
|
||||
mock_duco_client.async_get_info_overview.return_value = replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
diagnostic_subsystems=(
|
||||
DiagComponent(component="Ventilation", status=raw_status),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.BINARY_SENSOR])
|
||||
@@ -126,26 +124,31 @@ async def test_diagnostic_binary_sensors_added_after_initial_empty_response(
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test diagnostic binary sensors can be added after an empty response."""
|
||||
mock_duco_client.async_get_diagnostics_info.return_value = DiagInfo()
|
||||
mock_duco_client.async_get_info_overview.return_value = replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
diagnostic_subsystems=(),
|
||||
)
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.BINARY_SENSOR])
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
assert hass.states.get(VENTILATION_PROBLEM_ENTITY_ID) is None
|
||||
|
||||
mock_duco_client.async_get_diagnostics_info.return_value = DiagInfo(
|
||||
diagnostic_subsystems=(DiagComponent(component="Ventilation", status="Error"),)
|
||||
mock_duco_client.async_get_info_overview.return_value = replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
diagnostic_subsystems=(DiagComponent(component="Ventilation", status="Error"),),
|
||||
)
|
||||
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert hass.states.is_state(VENTILATION_PROBLEM_ENTITY_ID, STATE_ON)
|
||||
|
||||
mock_duco_client.async_get_diagnostics_info.return_value = DiagInfo(
|
||||
mock_duco_client.async_get_info_overview.return_value = replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
diagnostic_subsystems=(
|
||||
DiagComponent(component="Ventilation", status="Error"),
|
||||
DiagComponent(component="Filter", status="Ok"),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
@@ -198,8 +201,9 @@ async def test_diagnostic_binary_sensor_becomes_unknown_without_known_status(
|
||||
|
||||
assert hass.states.is_state(VENTILATION_PROBLEM_ENTITY_ID, STATE_OFF)
|
||||
|
||||
mock_duco_client.async_get_diagnostics_info.return_value = DiagInfo(
|
||||
diagnostic_subsystems=diagnostic_subsystems
|
||||
mock_duco_client.async_get_info_overview.return_value = replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
diagnostic_subsystems=diagnostic_subsystems,
|
||||
)
|
||||
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
@@ -224,14 +228,14 @@ async def test_diagnostics_refresh_failure_is_isolated_and_recovers(
|
||||
|
||||
assert hass.states.is_state(VENTILATION_PROBLEM_ENTITY_ID, STATE_OFF)
|
||||
|
||||
mock_duco_client.async_get_diagnostics_info.side_effect = exception_type("error")
|
||||
mock_duco_client.async_get_info_overview.side_effect = exception_type("error")
|
||||
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert hass.states.is_state(VENTILATION_PROBLEM_ENTITY_ID, STATE_UNAVAILABLE)
|
||||
assert hass.states.is_state("sensor.office_co2_carbon_dioxide", "405")
|
||||
|
||||
mock_duco_client.async_get_diagnostics_info.side_effect = None
|
||||
mock_duco_client.async_get_info_overview.side_effect = None
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert hass.states.is_state(VENTILATION_PROBLEM_ENTITY_ID, STATE_OFF)
|
||||
@@ -248,7 +252,7 @@ async def test_initial_diagnostics_failure_is_isolated_and_recovers(
|
||||
) -> None:
|
||||
"""Test an initial diagnostics failure is isolated and recovers."""
|
||||
mock_duco_client.async_get_nodes.return_value = mock_sensor_nodes
|
||||
mock_duco_client.async_get_diagnostics_info.side_effect = exception_type("error")
|
||||
mock_duco_client.async_get_info_overview.side_effect = exception_type("error")
|
||||
|
||||
await setup_platform_integration(
|
||||
hass, mock_config_entry, [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
@@ -258,7 +262,7 @@ async def test_initial_diagnostics_failure_is_isolated_and_recovers(
|
||||
assert hass.states.get(VENTILATION_PROBLEM_ENTITY_ID) is None
|
||||
assert hass.states.is_state("sensor.office_co2_carbon_dioxide", "405")
|
||||
|
||||
mock_duco_client.async_get_diagnostics_info.side_effect = None
|
||||
mock_duco_client.async_get_info_overview.side_effect = None
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert hass.states.is_state(VENTILATION_PROBLEM_ENTITY_ID, STATE_OFF)
|
||||
@@ -273,12 +277,12 @@ async def test_diagnostics_availability_transitions_logged(
|
||||
) -> None:
|
||||
"""Test diagnostics availability transitions are logged once."""
|
||||
caplog.set_level(logging.INFO, logger="homeassistant.components.duco.coordinator")
|
||||
mock_duco_client.async_get_diagnostics_info.side_effect = DucoError("error")
|
||||
mock_duco_client.async_get_info_overview.side_effect = DucoError("error")
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.BINARY_SENSOR])
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
mock_duco_client.async_get_diagnostics_info.side_effect = None
|
||||
mock_duco_client.async_get_info_overview.side_effect = None
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for the Duco integration setup."""
|
||||
|
||||
from dataclasses import replace
|
||||
from datetime import timedelta
|
||||
from unittest.mock import ANY, AsyncMock, patch
|
||||
|
||||
@@ -13,6 +14,7 @@ from duco_connectivity import (
|
||||
DucoConnectionError,
|
||||
DucoError,
|
||||
DucoResponseError,
|
||||
InfoOverview,
|
||||
LanInfo,
|
||||
Node,
|
||||
NodeListActionItemList,
|
||||
@@ -138,9 +140,15 @@ async def test_setup_entry_error(
|
||||
async def test_setup_entry_success(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_duco_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test successful setup of the Duco integration."""
|
||||
assert init_integration.state is ConfigEntryState.LOADED
|
||||
mock_duco_client.async_get_info_overview.assert_awaited_once_with()
|
||||
mock_duco_client.async_get_lan_info.assert_not_awaited()
|
||||
mock_duco_client.async_get_diagnostics_info.assert_not_awaited()
|
||||
mock_duco_client.async_get_time_filter_remaining.assert_not_awaited()
|
||||
mock_duco_client.async_get_ventilation_temperature_info.assert_not_awaited()
|
||||
|
||||
|
||||
async def test_device_via_device_links(
|
||||
@@ -180,14 +188,14 @@ async def test_device_via_device_links(
|
||||
pytest.param(DucoConnectionError("lan info offline"), id="connection_error"),
|
||||
],
|
||||
)
|
||||
async def test_setup_entry_ignores_lan_info_failures(
|
||||
async def test_setup_entry_ignores_info_overview_failures(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_duco_client: AsyncMock,
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
"""Test setup succeeds when the supplemental LAN info endpoint fails."""
|
||||
mock_duco_client.async_get_lan_info.side_effect = exception
|
||||
"""Test setup succeeds when the supplemental info overview fails."""
|
||||
mock_duco_client.async_get_info_overview.side_effect = exception
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
@@ -211,9 +219,12 @@ async def test_setup_entry_recovers_from_optional_temperature_capability_failure
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
"""Test an optional temperature capability is retried after a setup failure."""
|
||||
mock_duco_client.async_get_ventilation_temperature_info.side_effect = [
|
||||
mock_duco_client.async_get_info_overview.side_effect = [
|
||||
exception,
|
||||
VentilationTemperatureInfo(temp_oda=5.5),
|
||||
replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
ventilation_temperatures=VentilationTemperatureInfo(temp_oda=5.5),
|
||||
),
|
||||
]
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
@@ -443,6 +454,9 @@ async def test_setup_entry_creates_http_client(
|
||||
mock_client_class.return_value.async_get_node_actions.return_value = (
|
||||
mock_node_actions
|
||||
)
|
||||
mock_client_class.return_value.async_get_info_overview.return_value = (
|
||||
InfoOverview()
|
||||
)
|
||||
(
|
||||
mock_client_class.return_value.async_get_ventilation_temperature_info.return_value
|
||||
) = VentilationTemperatureInfo()
|
||||
@@ -470,6 +484,7 @@ async def test_setup_entry_creates_http_client(
|
||||
session=ANY,
|
||||
host=TEST_HOST,
|
||||
)
|
||||
mock_client_class.return_value.async_get_info_overview.assert_awaited_once_with()
|
||||
|
||||
|
||||
async def test_setup_entry_uses_configured_node_name(
|
||||
|
||||
@@ -7,6 +7,7 @@ from unittest.mock import AsyncMock
|
||||
from duco_connectivity import (
|
||||
DucoConnectionError,
|
||||
DucoError,
|
||||
InfoOverview,
|
||||
Node,
|
||||
NodeGeneralInfo,
|
||||
NodeSensorInfo,
|
||||
@@ -239,14 +240,14 @@ async def test_coordinator_update_failure_marks_unavailable(
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||
async def test_lan_info_failures_keep_node_entities_available(
|
||||
async def test_info_overview_failures_keep_node_entities_available(
|
||||
hass: HomeAssistant,
|
||||
mock_duco_client: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
"""Test node entities stay available when LAN info retrieval fails."""
|
||||
mock_duco_client.async_get_lan_info = AsyncMock(side_effect=exception)
|
||||
"""Test node entities stay available when info overview retrieval fails."""
|
||||
mock_duco_client.async_get_info_overview.side_effect = exception
|
||||
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
@@ -260,9 +261,9 @@ async def test_lan_info_failures_keep_node_entities_available(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"initial_time_filter_remain",
|
||||
"initial_info_overview_result",
|
||||
[
|
||||
pytest.param(None, id="missing"),
|
||||
pytest.param(InfoOverview(time_filter_remain=None), id="missing"),
|
||||
pytest.param(DucoError("heat recovery info error"), id="transient_failure"),
|
||||
],
|
||||
)
|
||||
@@ -272,13 +273,14 @@ async def test_time_filter_remaining_is_retried(
|
||||
mock_duco_client: AsyncMock,
|
||||
mock_sensor_nodes: list[Node],
|
||||
freezer: FrozenDateTimeFactory,
|
||||
initial_time_filter_remain: DucoError | None,
|
||||
initial_info_overview_result: InfoOverview | DucoError,
|
||||
) -> None:
|
||||
"""Test unavailable filter timer data is retried and can create the sensor."""
|
||||
mock_duco_client.async_get_nodes.return_value = mock_sensor_nodes
|
||||
mock_duco_client.async_get_time_filter_remaining.side_effect = [
|
||||
initial_time_filter_remain,
|
||||
180,
|
||||
info_overview = mock_duco_client.async_get_info_overview.return_value
|
||||
mock_duco_client.async_get_info_overview.side_effect = [
|
||||
initial_info_overview_result,
|
||||
info_overview,
|
||||
]
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.SENSOR])
|
||||
@@ -287,7 +289,7 @@ async def test_time_filter_remaining_is_retried(
|
||||
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert mock_duco_client.async_get_time_filter_remaining.await_count == 2
|
||||
assert mock_duco_client.async_get_info_overview.await_count == 2
|
||||
state = hass.states.get(FILTER_REMAINING_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == "180"
|
||||
@@ -300,9 +302,13 @@ async def test_empty_ventilation_temperatures_are_retried(
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test empty ventilation temperatures are retried and can appear later."""
|
||||
mock_duco_client.async_get_ventilation_temperature_info.side_effect = [
|
||||
VentilationTemperatureInfo(),
|
||||
VentilationTemperatureInfo(temp_oda=5.5),
|
||||
info_overview = mock_duco_client.async_get_info_overview.return_value
|
||||
mock_duco_client.async_get_info_overview.side_effect = [
|
||||
replace(info_overview, ventilation_temperatures=VentilationTemperatureInfo()),
|
||||
replace(
|
||||
info_overview,
|
||||
ventilation_temperatures=VentilationTemperatureInfo(temp_oda=5.5),
|
||||
),
|
||||
]
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.SENSOR])
|
||||
@@ -312,7 +318,7 @@ async def test_empty_ventilation_temperatures_are_retried(
|
||||
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert mock_duco_client.async_get_ventilation_temperature_info.await_count == 2
|
||||
assert mock_duco_client.async_get_info_overview.await_count == 2
|
||||
state = hass.states.get("sensor.living_outdoor_air_temperature")
|
||||
assert state is not None
|
||||
assert state.state == "5.5"
|
||||
@@ -324,8 +330,11 @@ async def test_partial_ventilation_temperatures_only_expose_available_sensor_val
|
||||
mock_duco_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test only populated ventilation temperature fields are exposed as states."""
|
||||
mock_duco_client.async_get_ventilation_temperature_info.return_value = (
|
||||
VentilationTemperatureInfo(temp_oda=5.5, temp_eta=21.4)
|
||||
mock_duco_client.async_get_info_overview.return_value = replace(
|
||||
mock_duco_client.async_get_info_overview.return_value,
|
||||
ventilation_temperatures=VentilationTemperatureInfo(
|
||||
temp_oda=5.5, temp_eta=21.4
|
||||
),
|
||||
)
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.SENSOR])
|
||||
@@ -571,7 +580,7 @@ async def test_previously_unknown_node_gets_entities_after_type_becomes_known(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_unknown_node_logged_at_debug(
|
||||
async def test_unknown_node_logged_at_debug_when_data_changes(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_duco_client: AsyncMock,
|
||||
@@ -579,7 +588,7 @@ async def test_unknown_node_logged_at_debug(
|
||||
freezer: FrozenDateTimeFactory,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that UNKNOWN nodes are logged at DEBUG level on every coordinator update."""
|
||||
"""Test that UNKNOWN nodes are logged at DEBUG level when data changes."""
|
||||
unknown_node = Node(
|
||||
node_id=99,
|
||||
general=NodeGeneralInfo(
|
||||
@@ -602,15 +611,24 @@ async def test_unknown_node_logged_at_debug(
|
||||
)
|
||||
mock_duco_client.async_get_nodes.return_value = [*mock_sensor_nodes, unknown_node]
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="homeassistant.components.duco"):
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert "has an unsupported device type" not in caplog.text
|
||||
|
||||
with caplog.at_level(logging.DEBUG, logger="homeassistant.components.duco"):
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert "has an unsupported device type" in caplog.text
|
||||
assert "has an unsupported device type" in caplog.text
|
||||
|
||||
caplog.clear()
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert "has an unsupported device type" not in caplog.text
|
||||
|
||||
caplog.clear()
|
||||
mock_duco_client.async_get_nodes.return_value = [
|
||||
*mock_sensor_nodes,
|
||||
replace(unknown_node, general=replace(unknown_node.general, identify=1)),
|
||||
]
|
||||
await async_fire_coordinator_update(hass, freezer)
|
||||
|
||||
assert "has an unsupported device type" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
|
||||
Reference in New Issue
Block a user