"""Tests for the Hyperion integration.""" from __future__ import annotations import asyncio import base64 from collections.abc import Awaitable, Callable from unittest.mock import AsyncMock, Mock, patch from aiohttp import web import pytest from homeassistant.components.camera import ( DEFAULT_CONTENT_TYPE, DOMAIN as CAMERA_DOMAIN, async_get_image, async_get_mjpeg_stream, ) from homeassistant.components.hyperion import get_hyperion_device_id from homeassistant.components.hyperion.const import ( DOMAIN, HYPERION_MANUFACTURER_NAME, HYPERION_MODEL_NAME, TYPE_HYPERION_CAMERA, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er from . import ( TEST_CONFIG_ENTRY_ID, TEST_INSTANCE, TEST_INSTANCE_1, TEST_SYSINFO_ID, async_call_registered_callback, create_mock_client, register_test_entity, setup_test_config_entry, ) TEST_CAMERA_ENTITY_ID = "camera.test_instance_1" TEST_IMAGE_DATA = "TEST DATA" TEST_IMAGE_UPDATE = { "command": "ledcolors-imagestream-update", "result": { "image": "data:image/jpg;base64," + base64.b64encode(TEST_IMAGE_DATA.encode()).decode("ascii"), }, "success": True, } async def test_camera_setup(hass: HomeAssistant) -> None: """Test turning the light on.""" client = create_mock_client() await setup_test_config_entry(hass, hyperion_client=client) # Verify switch is on (as per TEST_COMPONENTS above). entity_state = hass.states.get(TEST_CAMERA_ENTITY_ID) assert entity_state assert entity_state.state == "idle" async def test_camera_image(hass: HomeAssistant) -> None: """Test retrieving a single camera image.""" client = create_mock_client() client.async_send_image_stream_start = AsyncMock(return_value=True) client.async_send_image_stream_stop = AsyncMock(return_value=True) await setup_test_config_entry(hass, hyperion_client=client) get_image_coro = async_get_image(hass, TEST_CAMERA_ENTITY_ID) image_stream_update_coro = async_call_registered_callback( client, "ledcolors-imagestream-update", TEST_IMAGE_UPDATE ) result = await asyncio.gather(get_image_coro, image_stream_update_coro) assert client.async_send_image_stream_start.called assert client.async_send_image_stream_stop.called assert result[0].content == TEST_IMAGE_DATA.encode() async def test_camera_invalid_image(hass: HomeAssistant) -> None: """Test retrieving a single invalid camera image.""" client = create_mock_client() client.async_send_image_stream_start = AsyncMock(return_value=True) client.async_send_image_stream_stop = AsyncMock(return_value=True) await setup_test_config_entry(hass, hyperion_client=client) get_image_coro = async_get_image(hass, TEST_CAMERA_ENTITY_ID, timeout=0) image_stream_update_coro = async_call_registered_callback( client, "ledcolors-imagestream-update", None ) with pytest.raises(HomeAssistantError): await asyncio.gather(get_image_coro, image_stream_update_coro) get_image_coro = async_get_image(hass, TEST_CAMERA_ENTITY_ID, timeout=0) image_stream_update_coro = async_call_registered_callback( client, "ledcolors-imagestream-update", {"garbage": 1} ) with pytest.raises(HomeAssistantError): await asyncio.gather(get_image_coro, image_stream_update_coro) get_image_coro = async_get_image(hass, TEST_CAMERA_ENTITY_ID, timeout=0) image_stream_update_coro = async_call_registered_callback( client, "ledcolors-imagestream-update", {"result": {"image": "data:image/jpg;base64,FOO"}}, ) with pytest.raises(HomeAssistantError): await asyncio.gather(get_image_coro, image_stream_update_coro) async def test_camera_image_failed_start_stream_call(hass: HomeAssistant) -> None: """Test retrieving a single camera image with failed start stream call.""" client = create_mock_client() client.async_send_image_stream_start = AsyncMock(return_value=False) await setup_test_config_entry(hass, hyperion_client=client) with pytest.raises(HomeAssistantError): await async_get_image(hass, TEST_CAMERA_ENTITY_ID, timeout=0.01) assert client.async_send_image_stream_start.called assert not client.async_send_image_stream_stop.called async def test_camera_stream(hass: HomeAssistant) -> None: """Test retrieving a camera stream.""" client = create_mock_client() client.async_send_image_stream_start = AsyncMock(return_value=True) client.async_send_image_stream_stop = AsyncMock(return_value=True) request = Mock() async def fake_get_still_stream( in_request: web.Request, callback: Callable[[], Awaitable[bytes | None]], content_type: str, interval: float, ) -> bytes | None: assert request == in_request assert content_type == DEFAULT_CONTENT_TYPE assert interval == 0.0 return await callback() await setup_test_config_entry(hass, hyperion_client=client) with patch( "homeassistant.components.hyperion.camera.async_get_still_stream", ) as fake: fake.side_effect = fake_get_still_stream get_stream_coro = async_get_mjpeg_stream(hass, request, TEST_CAMERA_ENTITY_ID) image_stream_update_coro = async_call_registered_callback( client, "ledcolors-imagestream-update", TEST_IMAGE_UPDATE ) result = await asyncio.gather(get_stream_coro, image_stream_update_coro) assert client.async_send_image_stream_start.called assert client.async_send_image_stream_stop.called assert result[0] == TEST_IMAGE_DATA.encode() async def test_camera_stream_failed_start_stream_call(hass: HomeAssistant) -> None: """Test retrieving a camera stream with failed start stream call.""" client = create_mock_client() client.async_send_image_stream_start = AsyncMock(return_value=False) await setup_test_config_entry(hass, hyperion_client=client) request = Mock() assert not await async_get_mjpeg_stream(hass, request, TEST_CAMERA_ENTITY_ID) assert client.async_send_image_stream_start.called assert not client.async_send_image_stream_stop.called async def test_device_info(hass: HomeAssistant) -> None: """Verify device information includes expected details.""" client = create_mock_client() register_test_entity( hass, CAMERA_DOMAIN, TYPE_HYPERION_CAMERA, TEST_CAMERA_ENTITY_ID, ) await setup_test_config_entry(hass, hyperion_client=client) device_id = get_hyperion_device_id(TEST_SYSINFO_ID, TEST_INSTANCE) device_registry = dr.async_get(hass) device = device_registry.async_get_device(identifiers={(DOMAIN, device_id)}) assert device assert device.config_entries == {TEST_CONFIG_ENTRY_ID} assert device.identifiers == {(DOMAIN, device_id)} assert device.manufacturer == HYPERION_MANUFACTURER_NAME assert device.model == HYPERION_MODEL_NAME assert device.name == TEST_INSTANCE_1["friendly_name"] entity_registry = er.async_get(hass) entities_from_device = [ entry.entity_id for entry in er.async_entries_for_device(entity_registry, device.id) ] assert TEST_CAMERA_ENTITY_ID in entities_from_device