diff --git a/homeassistant/components/collection_image/image.py b/homeassistant/components/collection_image/image.py index ad601054ee9f..d01ddfd2cbc7 100644 --- a/homeassistant/components/collection_image/image.py +++ b/homeassistant/components/collection_image/image.py @@ -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)) diff --git a/homeassistant/components/collection_image/quality_scale.yaml b/homeassistant/components/collection_image/quality_scale.yaml index 34c064ff239f..2d86de188c75 100644 --- a/homeassistant/components/collection_image/quality_scale.yaml +++ b/homeassistant/components/collection_image/quality_scale.yaml @@ -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 diff --git a/homeassistant/components/collection_image/strings.json b/homeassistant/components/collection_image/strings.json index 2106c05a7011..2b252132ea59 100644 --- a/homeassistant/components/collection_image/strings.json +++ b/homeassistant/components/collection_image/strings.json @@ -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": { diff --git a/tests/components/collection_image/test_image.py b/tests/components/collection_image/test_image.py index 05a3a6206059..09bd6efb380a 100644 --- a/tests/components/collection_image/test_image.py +++ b/tests/components/collection_image/test_image.py @@ -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) diff --git a/tests/components/collection_image/test_services.py b/tests/components/collection_image/test_services.py index 97ff1ffd54fb..94e9ce7efb4b 100644 --- a/tests/components/collection_image/test_services.py +++ b/tests/components/collection_image/test_services.py @@ -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