"""Axis light platform tests.""" import json import re from typing import Any from unittest.mock import patch from axis.models.api import CONTEXT import pytest from syrupy.assertion import SnapshotAssertion from yarl import URL from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, Platform, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from .conftest import ConfigEntryFactoryType, RtspEventMock from .const import DEFAULT_HOST, NAME from tests.common import snapshot_platform from tests.test_util.aiohttp import AiohttpClientMocker, AiohttpClientMockResponse API_DISCOVERY_LIGHT_CONTROL = { "id": "light-control", "version": "1.1", "name": "Light Control", } @pytest.fixture def light_control_items() -> list[dict[str, Any]]: """Available lights.""" return [ { "lightID": "led0", "lightType": "IR", "enabled": True, "synchronizeDayNightMode": True, "lightState": False, "automaticIntensityMode": False, "automaticAngleOfIlluminationMode": False, "nrOfLEDs": 1, "error": False, "errorInfo": "", } ] @pytest.fixture(autouse=True) def light_control_fixture( aioclient_mock: AiohttpClientMocker, light_control_items: list[dict[str, Any]], ) -> None: """Light control mock response.""" async def _light_control_response( _method: str, url: URL, data: bytes | dict[str, Any] | None ) -> AiohttpClientMockResponse: payload: dict[str, Any] if isinstance(data, bytes): payload = json.loads(data) elif isinstance(data, dict): payload = data else: payload = {} request_method = payload.get("method") if request_method == "getCurrentIntensity": response_data = { "apiVersion": "1.1", "context": "Axis library", "method": "getCurrentIntensity", "data": {"intensity": 100}, } elif request_method == "getValidIntensity": response_data = { "apiVersion": "1.1", "context": "Axis library", "method": "getValidIntensity", "data": {"ranges": [{"low": 0, "high": 150}]}, } else: response_data = { "apiVersion": "1.1", "context": CONTEXT, "method": "getLightInformation", "data": {"items": light_control_items}, } return AiohttpClientMockResponse("post", url, json=response_data) aioclient_mock.post( re.compile( rf"^https?://{re.escape(DEFAULT_HOST)}(?::\d+)?/axis-cgi/lightcontrol.cgi$" ), side_effect=_light_control_response, ) @pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_LIGHT_CONTROL]) @pytest.mark.parametrize("light_control_items", [[]]) @pytest.mark.usefixtures("config_entry_setup") async def test_no_light_entity_without_light_control_representation( hass: HomeAssistant, mock_rtsp_event: RtspEventMock, ) -> None: """Verify no lights entities get created without light control representation.""" mock_rtsp_event( topic="tns1:Device/tnsaxis:Light/Status", data_type="state", data_value="ON", source_name="id", source_idx="0", ) await hass.async_block_till_done() assert not hass.states.async_entity_ids(LIGHT_DOMAIN) @pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_LIGHT_CONTROL]) async def test_lights( hass: HomeAssistant, entity_registry: er.EntityRegistry, config_entry_factory: ConfigEntryFactoryType, mock_rtsp_event: RtspEventMock, snapshot: SnapshotAssertion, ) -> None: """Test that lights are loaded properly.""" with patch("homeassistant.components.axis.PLATFORMS", [Platform.LIGHT]): config_entry = await config_entry_factory() mock_rtsp_event( topic="tns1:Device/tnsaxis:Light/Status", data_type="state", data_value="ON", source_name="id", source_idx="0", ) await hass.async_block_till_done() await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id) entity_id = f"{LIGHT_DOMAIN}.{NAME}_ir_light_0" # Turn on, set brightness, light already on with ( patch("axis.interfaces.vapix.LightHandler.activate_light") as mock_activate, patch( "axis.interfaces.vapix.LightHandler.set_manual_intensity" ) as mock_set_intensity, ): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 50}, blocking=True, ) mock_activate.assert_not_awaited() mock_set_intensity.assert_called_once_with("led0", 29) # Turn off with patch( "axis.interfaces.vapix.LightHandler.deactivate_light" ) as mock_deactivate: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True, ) mock_deactivate.assert_called_once() # Event turn off light mock_rtsp_event( topic="tns1:Device/tnsaxis:Light/Status", data_type="state", data_value="OFF", source_name="id", source_idx="0", ) await hass.async_block_till_done() light_0 = hass.states.get(entity_id) assert light_0.state == STATE_OFF # Turn on, set brightness with ( patch("axis.interfaces.vapix.LightHandler.activate_light") as mock_activate, patch( "axis.interfaces.vapix.LightHandler.set_manual_intensity" ) as mock_set_intensity, ): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True, ) mock_activate.assert_called_once() mock_set_intensity.assert_not_called() # Turn off, light already off with patch( "axis.interfaces.vapix.LightHandler.deactivate_light" ) as mock_deactivate: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True, ) mock_deactivate.assert_not_called()