Service exceptions for collection_image (#182442)

This commit is contained in:
karwosts
2026-09-17 18:18:55 +02:00
committed by GitHub
parent fe9fa47ff2
commit 836c5cbfec
5 changed files with 59 additions and 28 deletions
@@ -80,6 +80,11 @@ class CollectionImageImageEntity(ImageEntity):
self._attr_image_url = UNDEFINED
self._cached_image = None
self.async_write_ha_state()
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="no_images",
translation_placeholders={"entity": self.entity_id},
)
async def get_valid_images(self) -> list[BrowseMedia]:
"""Given the configured media directory for the entity, get a list of all child images."""
@@ -107,15 +112,15 @@ class CollectionImageImageEntity(ImageEntity):
media_content_id,
)
if not images:
self.set_unavailable()
return images
async def get_random_image(self) -> None:
"""Update the image entity with a random image from the source media."""
filtered = await self.get_valid_images()
if not filtered:
self.set_unavailable()
return
# Don't allow random shuffle to return the same image we are currently viewing.
if self._current_image_id:
@@ -151,9 +156,6 @@ class CollectionImageImageEntity(ImageEntity):
"""Get the first or last image."""
filtered = await self.get_valid_images()
if not filtered:
self.set_unavailable()
return
child = filtered[position]
self._attr_available = True
@@ -165,9 +167,6 @@ class CollectionImageImageEntity(ImageEntity):
"""Get the next or previous image."""
filtered = await self.get_valid_images()
if not filtered:
self.set_unavailable()
return
current_index = next(
(
@@ -197,13 +196,19 @@ class CollectionImageImageEntity(ImageEntity):
try:
resolved = await async_resolve_media(self.hass, image_id, self.entity_id)
except Unresolvable as err:
_LOGGER.warning("%s: %s", self.entity_id, str(err))
self._attr_image_last_updated = None
self.path = None
self._attr_image_url = UNDEFINED
self._attr_content_type = DEFAULT_CONTENT_TYPE
self.async_write_ha_state()
return
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unresolvable",
translation_placeholders={
"entity": self.entity_id,
"id": image_id,
},
) from err
finally:
self._current_image_id = image_id
@@ -223,7 +228,12 @@ class CollectionImageImageEntity(ImageEntity):
"""Initialize the first image after entity has been created."""
async def get_random_image_on_start(_hass: HomeAssistant) -> None:
await self.get_random_image()
try:
await self.get_random_image()
except HomeAssistantError:
_LOGGER.exception(
"Unable to get an initial image",
)
self.async_on_remove(async_at_started(self.hass, get_random_image_on_start))
@@ -44,7 +44,7 @@ rules:
comment: Integration does not connect to any physical object or service.
# Silver
action-exceptions: todo
action-exceptions: done
config-entry-unloading: done
docs-configuration-parameters: done
docs-installation-parameters: done
@@ -30,6 +30,12 @@
"exceptions": {
"image_read_error": {
"message": "Error reading image from {path}: {error}"
},
"no_images": {
"message": "No image files were found in the configured media. {entity} will now be unavailable. Reload the config entry after correcting the media source."
},
"unresolvable": {
"message": "Selected media id {id} failed to resolve for {entity}."
}
},
"services": {
@@ -270,6 +270,8 @@ async def test_media_error(
for err in error_messages:
assert err in caplog.text
assert "No image files were found in the configured media" in caplog.text
client = await hass_client()
resp = await client.get(f"/api/image_proxy/{DEFAULT_ENTITY_ID}")
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
@@ -301,7 +303,15 @@ async def test_unresolvable(
await hass.async_block_till_done(wait_background_tasks=True)
assert "image.random_image: Mock image failed to resolve" in caplog.text
assert "Mock image failed to resolve" in caplog.text
with pytest.raises(HomeAssistantError, match="failed to resolve"):
await hass.services.async_call(
DOMAIN,
"shuffle",
{ATTR_ENTITY_ID: DEFAULT_ENTITY_ID},
blocking=True,
)
# Test we can recover by calling shuffle again when the image is resolvable
del media_source_state.resolve_exceptions[MOCK_MEDIA_IMAGE_URI_1]
@@ -316,8 +326,8 @@ async def test_unresolvable(
blocking=True,
)
assert mock_media_source.image_browse.call_count == 2
assert mock_media_source.resolve.call_count == 2
assert mock_media_source.image_browse.call_count == 3
assert mock_media_source.resolve.call_count == 3
state = hass.states.get(DEFAULT_ENTITY_ID)
@@ -2,6 +2,8 @@
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.collection_image.const import DOMAIN
from homeassistant.components.collection_image.image import CollectionImageImageEntity
from homeassistant.components.collection_image.services import (
@@ -11,6 +13,7 @@ from homeassistant.components.collection_image.services import (
from homeassistant.components.media_source import PlayMedia
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .conftest import MediaSourceMocks, MediaSourceState
from .const import DEFAULT_ENTITY_ID, MOCK_MEDIA_DIR_URI_1
@@ -176,12 +179,13 @@ async def test_navigation(
# Now there are no images, go to unavailable
media_source_state.browse_results = {MOCK_MEDIA_DIR_URI_1: directory("My pictures")}
await hass.services.async_call(
DOMAIN,
CollectionImageService.SELECT_NEXT,
data,
blocking=True,
)
with pytest.raises(HomeAssistantError, match="No image files were found"):
await hass.services.async_call(
DOMAIN,
CollectionImageService.SELECT_NEXT,
data,
blocking=True,
)
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == STATE_UNAVAILABLE
@@ -214,11 +218,12 @@ async def test_first_unavailable(
assert state and state.state != STATE_UNAVAILABLE
media_source_state.browse_results = {MOCK_MEDIA_DIR_URI_1: directory("My pictures")}
await hass.services.async_call(
DOMAIN,
CollectionImageService.SELECT_FIRST,
{ATTR_ENTITY_ID: DEFAULT_ENTITY_ID},
blocking=True,
)
with pytest.raises(HomeAssistantError, match="No image files were found"):
await hass.services.async_call(
DOMAIN,
CollectionImageService.SELECT_FIRST,
{ATTR_ENTITY_ID: DEFAULT_ENTITY_ID},
blocking=True,
)
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == STATE_UNAVAILABLE