Files

266 lines
8.4 KiB
Python

"""The tests for the Collection Image image platform."""
from http import HTTPStatus
from pathlib import Path
from unittest.mock import AsyncMock, patch
from freezegun import freeze_time
import pytest
from homeassistant.components.image import Image, async_get_image
from homeassistant.components.media_source import BrowseMediaSource, PlayMedia
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, STATE_UNAVAILABLE
from homeassistant.core import CoreState, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .const import DEFAULT_ENTITY_ID, TEST_IMAGE
from tests.common import MockConfigEntry
from tests.typing import ClientSessionGenerator
TEST_TIME = "2025-11-08T12:00:00+00:00"
async def test_image(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
config_entry: MockConfigEntry,
mock_media_source,
) -> None:
"""Test loading an image."""
with (
freeze_time(TEST_TIME),
):
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == TEST_TIME
client = await hass_client()
resp = await client.get(f"/api/image_proxy/{DEFAULT_ENTITY_ID}")
assert resp.status == HTTPStatus.OK
assert resp.content_type == "image/png"
expected_data = await hass.async_add_executor_job(TEST_IMAGE.read_bytes)
body = await resp.read()
assert body == expected_data
async def test_image_during_startup(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
config_entry: MockConfigEntry,
mock_media_source,
) -> None:
"""Test loading an image, ensuring that we don't browse until after startup is complete."""
with freeze_time(TEST_TIME):
hass.set_state(CoreState.starting)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
hass.set_state(CoreState.running)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == TEST_TIME
client = await hass_client()
resp = await client.get(f"/api/image_proxy/{DEFAULT_ENTITY_ID}")
assert resp.status == HTTPStatus.OK
assert resp.content_type == "image/png"
expected_data = await hass.async_add_executor_job(TEST_IMAGE.read_bytes)
body = await resp.read()
assert body == expected_data
async def test_image_url(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
config_entry: MockConfigEntry,
browse_media_result: BrowseMediaSource,
) -> None:
"""Test loading an image, when media resolves to a URL."""
expected_data = await hass.async_add_executor_job(TEST_IMAGE.read_bytes)
with (
freeze_time(TEST_TIME),
patch(
"homeassistant.components.collection_image.image.async_browse_media",
return_value=browse_media_result,
),
patch(
"homeassistant.components.collection_image.image.async_resolve_media",
return_value=PlayMedia(
url="http://example.com/test.png",
mime_type="image/png",
),
),
):
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == TEST_TIME
client = await hass_client()
with patch(
"homeassistant.components.collection_image.image.CollectionImageImageEntity._async_load_image_from_url",
new_callable=AsyncMock,
) as mock_load:
mock_load.return_value = Image(
content_type="image/png",
content=expected_data,
)
resp = await client.get(f"/api/image_proxy/{DEFAULT_ENTITY_ID}")
mock_load.assert_awaited_once_with("http://example.com/test.png")
assert resp.status == HTTPStatus.OK
assert resp.content_type == "image/png"
body = await resp.read()
assert body == expected_data
async def test_no_images(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test when there are no images in the media folder."""
with patch(
"homeassistant.components.collection_image.image.async_browse_media",
return_value=BrowseMediaSource(
domain=None,
identifier=None,
media_class="",
media_content_type="",
title="",
can_play=False,
can_expand=True,
children=[],
),
):
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == STATE_UNAVAILABLE
await hass.async_block_till_done(wait_background_tasks=True)
assert (
"image.random_image: No valid images in media-source://mymedia" 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
async def test_media_error(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test when media browse throws an error."""
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == STATE_UNAVAILABLE
await hass.async_block_till_done(wait_background_tasks=True)
assert "image.random_image: Media Source not loaded" 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
async def test_unresolvable(
hass: HomeAssistant,
config_entry: MockConfigEntry,
browse_media_result: BrowseMediaSource,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test when resolving an image fails."""
with (
patch(
"homeassistant.components.collection_image.image.async_browse_media",
return_value=browse_media_result,
),
):
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == STATE_UNAVAILABLE
await hass.async_block_till_done(wait_background_tasks=True)
assert "image.random_image: Media Source not loaded" in caplog.text
async def test_image_file_read_error(
hass: HomeAssistant,
config_entry: MockConfigEntry,
browse_media_result: BrowseMediaSource,
hass_client: ClientSessionGenerator,
) -> None:
"""Test that a file read error is surfaced when serving the image."""
missing_path = Path(__file__).parent / "does_not_exist.png"
with (
freeze_time(TEST_TIME),
patch(
"homeassistant.components.collection_image.image.async_browse_media",
return_value=browse_media_result,
),
patch(
"homeassistant.components.collection_image.image.async_resolve_media",
return_value=PlayMedia(
url="",
mime_type="image/png",
path=missing_path,
),
),
):
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Browse and resolve succeeded, so the entity is available with an image.
state = hass.states.get(DEFAULT_ENTITY_ID)
assert state and state.state == TEST_TIME
with pytest.raises(HomeAssistantError) as exc_info:
await async_get_image(hass, DEFAULT_ENTITY_ID)
assert exc_info.value.translation_key == "image_read_error"
assert exc_info.value.translation_placeholders["path"] == str(missing_path)
client = await hass_client()
resp = await client.get(f"/api/image_proxy/{DEFAULT_ENTITY_ID}")
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR