"""Tests for the Hyperion integration.""" from __future__ import annotations import asyncio import base64 from collections.abc import Awaitable import logging from typing import 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, ) _LOGGER = logging.getLogger(__name__) 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) 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({(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 = await er.async_get_registry(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