From 32d8b3719d15f76c6657593370eed6c2b54d59eb Mon Sep 17 00:00:00 2001 From: Ronald van der Meer Date: Sun, 26 Apr 2026 15:42:32 +0200 Subject: [PATCH 1/3] Fix diagnostics crash on connection error in Duco integration --- homeassistant/components/duco/diagnostics.py | 18 ++++++++----- homeassistant/components/duco/strings.json | 3 +++ tests/components/duco/test_diagnostics.py | 28 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/duco/diagnostics.py b/homeassistant/components/duco/diagnostics.py index 78a23db7a9cfb2..11e65e1ac1baa2 100644 --- a/homeassistant/components/duco/diagnostics.py +++ b/homeassistant/components/duco/diagnostics.py @@ -2,13 +2,15 @@ from __future__ import annotations -import asyncio from dataclasses import asdict from typing import Any +from duco.exceptions import DucoConnectionError + from homeassistant.components.diagnostics import async_redact_data from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .coordinator import DucoConfigEntry @@ -32,11 +34,15 @@ async def async_get_config_entry_diagnostics( board = asdict(coordinator.board_info) board.pop("time") - lan_info, duco_diags, write_remaining = await asyncio.gather( - coordinator.client.async_get_lan_info(), - coordinator.client.async_get_diagnostics(), - coordinator.client.async_get_write_req_remaining(), - ) + try: + lan_info = await coordinator.client.async_get_lan_info() + duco_diags = await coordinator.client.async_get_diagnostics() + write_remaining = await coordinator.client.async_get_write_req_remaining() + except DucoConnectionError as err: + raise HomeAssistantError( + translation_domain="duco", + translation_key="diagnostics_connection_error", + ) from err return async_redact_data( { diff --git a/homeassistant/components/duco/strings.json b/homeassistant/components/duco/strings.json index af8f86f39798b0..ab7d7cde0955aa 100644 --- a/homeassistant/components/duco/strings.json +++ b/homeassistant/components/duco/strings.json @@ -87,6 +87,9 @@ "cannot_connect": { "message": "An error occurred while trying to connect to the Duco instance: {error}" }, + "diagnostics_connection_error": { + "message": "Failed to retrieve diagnostics data: could not connect to the Duco device." + }, "failed_to_set_state": { "message": "Failed to set ventilation state: {error}" }, diff --git a/tests/components/duco/test_diagnostics.py b/tests/components/duco/test_diagnostics.py index 2b5671d8f7b0d6..4f42ec97e8a889 100644 --- a/tests/components/duco/test_diagnostics.py +++ b/tests/components/duco/test_diagnostics.py @@ -2,12 +2,16 @@ from __future__ import annotations +from http import HTTPStatus from unittest.mock import AsyncMock +from duco.exceptions import DucoConnectionError import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.diagnostics import DOMAIN as DIAGNOSTICS_DOMAIN from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry @@ -27,3 +31,27 @@ async def test_diagnostics( await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry) == snapshot ) + + +@pytest.mark.usefixtures("init_integration") +@pytest.mark.parametrize( + "failing_method", + ["async_get_lan_info", "async_get_diagnostics", "async_get_write_req_remaining"], +) +async def test_diagnostics_connection_error( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_config_entry: MockConfigEntry, + mock_duco_client: AsyncMock, + failing_method: str, +) -> None: + """Test that a connection error during diagnostics returns a 500 response.""" + getattr(mock_duco_client, failing_method).side_effect = DucoConnectionError( + "Server disconnected" + ) + assert await async_setup_component(hass, DIAGNOSTICS_DOMAIN, {}) + client = await hass_client() + response = await client.get( + f"/api/diagnostics/config_entry/{mock_config_entry.entry_id}" + ) + assert response.status == HTTPStatus.INTERNAL_SERVER_ERROR From c7b926692af091427b5846ccf5bba86264617504 Mon Sep 17 00:00:00 2001 From: Ronald van der Meer Date: Mon, 27 Apr 2026 22:39:51 +0200 Subject: [PATCH 2/3] Rename diagnostics_connection_error to connection_error for reusability --- homeassistant/components/duco/diagnostics.py | 5 +++-- homeassistant/components/duco/strings.json | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/duco/diagnostics.py b/homeassistant/components/duco/diagnostics.py index 11e65e1ac1baa2..e21079984ed9c7 100644 --- a/homeassistant/components/duco/diagnostics.py +++ b/homeassistant/components/duco/diagnostics.py @@ -12,6 +12,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from .const import DOMAIN from .coordinator import DucoConfigEntry TO_REDACT = { @@ -40,8 +41,8 @@ async def async_get_config_entry_diagnostics( write_remaining = await coordinator.client.async_get_write_req_remaining() except DucoConnectionError as err: raise HomeAssistantError( - translation_domain="duco", - translation_key="diagnostics_connection_error", + translation_domain=DOMAIN, + translation_key="connection_error", ) from err return async_redact_data( diff --git a/homeassistant/components/duco/strings.json b/homeassistant/components/duco/strings.json index ab7d7cde0955aa..da70af200e10ef 100644 --- a/homeassistant/components/duco/strings.json +++ b/homeassistant/components/duco/strings.json @@ -87,8 +87,8 @@ "cannot_connect": { "message": "An error occurred while trying to connect to the Duco instance: {error}" }, - "diagnostics_connection_error": { - "message": "Failed to retrieve diagnostics data: could not connect to the Duco device." + "connection_error": { + "message": "Could not connect to the Duco device." }, "failed_to_set_state": { "message": "Failed to set ventilation state: {error}" From 57be10633186f83bd90f34a7f9789d87ec092b80 Mon Sep 17 00:00:00 2001 From: Ronald van der Meer Date: Mon, 27 Apr 2026 23:12:23 +0200 Subject: [PATCH 3/3] Add async_block_till_done after async_setup_component in diagnostics test --- tests/components/duco/test_diagnostics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/duco/test_diagnostics.py b/tests/components/duco/test_diagnostics.py index 4f42ec97e8a889..8cfd8569546797 100644 --- a/tests/components/duco/test_diagnostics.py +++ b/tests/components/duco/test_diagnostics.py @@ -50,6 +50,7 @@ async def test_diagnostics_connection_error( "Server disconnected" ) assert await async_setup_component(hass, DIAGNOSTICS_DOMAIN, {}) + await hass.async_block_till_done() client = await hass_client() response = await client.get( f"/api/diagnostics/config_entry/{mock_config_entry.entry_id}"