From 4cf5509bc17a5166f5ff1cc917bd45c32fb0087f Mon Sep 17 00:00:00 2001 From: Ronald van der Meer Date: Thu, 11 Jun 2026 22:28:08 +0200 Subject: [PATCH] Fix Duco system health for multiple loaded entries (#173324) --- .../components/duco/system_health.py | 34 +++++++++++++++++-- tests/components/duco/test_system_health.py | 28 +++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/duco/system_health.py b/homeassistant/components/duco/system_health.py index 72c43c9980a2..882b04d726d6 100644 --- a/homeassistant/components/duco/system_health.py +++ b/homeassistant/components/duco/system_health.py @@ -31,6 +31,29 @@ async def _async_get_write_requests_remaining( return {"type": "failed", "error": "unreachable"} +def _entry_write_requests_remaining_key(config_entry: DucoConfigEntry) -> str: + """Return the identifying label for a config entry quota.""" + identifier = config_entry.unique_id or config_entry.entry_id + return f"{config_entry.title or config_entry.entry_id} ({identifier})" + + +async def _async_get_write_requests_remaining_summary( + config_entries: list[DucoConfigEntry], +) -> str: + """Get a per-entry write-request summary for system health.""" + # Keep one translated system health label; multiple Duco boxes are + # summarized in the value to avoid ambiguous per-entry labels. + summaries: list[str] = [] + for config_entry in config_entries: + result = await _async_get_write_requests_remaining(config_entry) + summaries.append( + f"{_entry_write_requests_remaining_key(config_entry)}: " + f"{result if not isinstance(result, dict) else f'Failed: {result["error"]}'}" + ) + + return "; ".join(summaries) + + async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: """Get info for the info page.""" config_entries: list[DucoConfigEntry] = hass.config_entries.async_loaded_entries( @@ -40,8 +63,15 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: if not config_entries: return {} + if len(config_entries) == 1: + return { + "write_requests_remaining": _async_get_write_requests_remaining( + config_entries[0] + ) + } + return { - "write_requests_remaining": _async_get_write_requests_remaining( - config_entries[0] + "write_requests_remaining": _async_get_write_requests_remaining_summary( + config_entries ) } diff --git a/tests/components/duco/test_system_health.py b/tests/components/duco/test_system_health.py index c351f7688c4b..96ea6a12547f 100644 --- a/tests/components/duco/test_system_health.py +++ b/tests/components/duco/test_system_health.py @@ -5,9 +5,12 @@ from unittest.mock import AsyncMock from duco_connectivity.exceptions import DucoConnectionError from homeassistant.components.duco.const import DOMAIN +from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from . import setup_integration + from tests.common import MockConfigEntry, get_system_health_info @@ -53,3 +56,28 @@ async def test_system_health_no_loaded_entries(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert await get_system_health_info(hass, DOMAIN) == {} + + +async def test_system_health_multiple_loaded_entries( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_duco_client: AsyncMock, +) -> None: + """Test system health aggregates quotas for multiple loaded Duco boxes.""" + second_entry = MockConfigEntry( + title="SECOND_BOX", + domain=DOMAIN, + data={CONF_HOST: "192.168.1.101"}, + unique_id="aa:bb:cc:dd:ee:00", + ) + await setup_integration(hass, second_entry) + mock_duco_client.async_get_write_requests_remaining.side_effect = [100, 75] + + assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() + + info = await get_system_health_info(hass, DOMAIN) + + assert await info["write_requests_remaining"] == ( + "SILENT_CONNECT (aa:bb:cc:dd:ee:ff): 100; SECOND_BOX (aa:bb:cc:dd:ee:00): 75" + )