From e81abcf8cd500f99b818a54b44dd9766dcace24f Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Thu, 27 Aug 2026 19:39:40 +0200 Subject: [PATCH] Ensure gardena image goes back to unknown on render fails (#180431) --- .../components/gardena_bluetooth/image.py | 10 +++++- .../snapshots/test_image.ambr | 2 -- .../gardena_bluetooth/test_image.py | 35 +++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/gardena_bluetooth/image.py b/homeassistant/components/gardena_bluetooth/image.py index a6e23e3d362f..4648bc6901a0 100644 --- a/homeassistant/components/gardena_bluetooth/image.py +++ b/homeassistant/components/gardena_bluetooth/image.py @@ -97,9 +97,17 @@ class GardenaBluetoothImage(GardenaBluetoothEntity, ImageEntity): def _handle_coordinator_update(self) -> None: if (image := self._render()) != self._image: self._image = image - self._attr_image_last_updated = dt_util.utcnow() + self._attr_image_last_updated = dt_util.utcnow() if image else None super()._handle_coordinator_update() + @property + @override + def entity_picture(self) -> str | None: + """Return a link to the image, or none while there is nothing to show.""" + if self._image is None: + return None + return super().entity_picture + @override async def async_image(self) -> bytes | None: """Return bytes of image.""" diff --git a/tests/components/gardena_bluetooth/snapshots/test_image.ambr b/tests/components/gardena_bluetooth/snapshots/test_image.ambr index 52f1576cace1..0ad44b241a39 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_image.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_image.ambr @@ -254,7 +254,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ : '1', - : '/api/image_proxy/image.mock_title_contour_4?token=1', : 'Mock Title Contour 4', }), 'context': , @@ -306,7 +305,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ : '1', - : '/api/image_proxy/image.mock_title_contour_5?token=1', : 'Mock Title Contour 5', }), 'context': , diff --git a/tests/components/gardena_bluetooth/test_image.py b/tests/components/gardena_bluetooth/test_image.py index ee3ed1e63e7e..243316af64ff 100644 --- a/tests/components/gardena_bluetooth/test_image.py +++ b/tests/components/gardena_bluetooth/test_image.py @@ -154,12 +154,47 @@ async def test_image_without_contour( state = hass.states.get("image.mock_title_contour_4") assert state assert state.state == "unknown" + assert "entity_picture" not in state.attributes client = await hass_client() resp = await client.get("/api/image_proxy/image.mock_title_contour_4") assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR +@pytest.mark.usefixtures("mock_contours") +async def test_contour_cleared_after_being_present( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_read_char_raw: dict[str, bytes | Exception], + scan_step: Callable[[], Awaitable[None]], +) -> None: + """Test a contour that is taught and then cleared goes back to unknown.""" + await setup_entry( + hass, platforms=[Platform.IMAGE], service_info=AQUA_CONTOUR_SERVICE_INFO + ) + + state = hass.states.get(ENTITY_ID) + assert state + assert state.state != "unknown" + assert "entity_picture" in state.attributes + + client = await hass_client() + resp = await client.get(f"/api/image_proxy/{ENTITY_ID}") + assert resp.status == HTTPStatus.OK + + mock_read_char_raw[AquaContourContours.contour_points_1.unique_id] = b"" + for _ in range(SEGMENTED_SCAN_COUNT): + await scan_step() + + state = hass.states.get(ENTITY_ID) + assert state + assert state.state == "unknown" + assert "entity_picture" not in state.attributes + + resp = await client.get(f"/api/image_proxy/{ENTITY_ID}") + assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR + + async def test_active_contour_follows_position( hass: HomeAssistant, hass_client: ClientSessionGenerator,