"""Test the Avea light platform.""" from collections.abc import AsyncGenerator from datetime import timedelta from unittest.mock import MagicMock, call, patch from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.avea.const import UNKNOWN_NAME from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, LightEntityStateAttribute, ) from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from . import AVEA_DISCOVERY_INFO, AVEA_FIRMWARE_VERSION, AVEA_SERIAL_NUMBER from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform @pytest.fixture def mock_bulb() -> MagicMock: """Return a mocked Avea bulb.""" bulb = MagicMock() bulb.name = "Unknown" bulb.fw_version = "Unknown" bulb.hardware_revision = "Unknown" bulb.manufacturer_name = "Unknown" bulb.serial_number = "Unknown" bulb.brightness = 0 bulb.connect.return_value = True bulb.get_brightness.return_value = 0 bulb.get_fw_version.return_value = AVEA_FIRMWARE_VERSION bulb.get_hardware_revision.return_value = "Elgato Avea" bulb.get_manufacturer_name.return_value = "Elgato Systems GmbH" bulb.get_rgb.return_value = (0, 0, 0) bulb.get_serial_number.return_value = AVEA_SERIAL_NUMBER return bulb @pytest.fixture async def setup_integration( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_bulb: MagicMock, ) -> AsyncGenerator[MagicMock]: """Set up the integration.""" with ( patch( "homeassistant.components.avea.async_ble_device_from_address", return_value=AVEA_DISCOVERY_INFO.device, ), patch("homeassistant.components.avea.avea.Bulb", return_value=mock_bulb), ): mock_config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() yield mock_bulb @pytest.mark.usefixtures("setup_integration") async def test_all_entities( hass: HomeAssistant, entity_registry: er.EntityRegistry, mock_config_entry: MockConfigEntry, snapshot: SnapshotAssertion, ) -> None: """Test all entities.""" await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) async def test_device_info( device_registry: dr.DeviceRegistry, mock_config_entry: MockConfigEntry, setup_integration: MagicMock, ) -> None: """Test the device info.""" bulb = setup_integration device = device_registry.async_get_device_by_connection( (dr.CONNECTION_BLUETOOTH, AVEA_DISCOVERY_INFO.address), mock_config_entry.entry_id, ) assert device is not None assert device.name == "Bedroom" assert device.manufacturer == "Elgato Systems GmbH" assert device.model == "Avea" assert device.hw_version == "Elgato Avea" assert device.sw_version == AVEA_FIRMWARE_VERSION assert device.serial_number == AVEA_SERIAL_NUMBER bulb.get_manufacturer_name.assert_called_once() bulb.get_hardware_revision.assert_called_once() bulb.get_fw_version.assert_called_once() bulb.get_serial_number.assert_called_once() async def test_device_info_populates_when_connect_fails( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_config_entry: MockConfigEntry, mock_bulb: MagicMock, ) -> None: """Test device info is populated when the shared connection fails.""" mock_bulb.connect.return_value = False with ( patch( "homeassistant.components.avea.async_ble_device_from_address", return_value=AVEA_DISCOVERY_INFO.device, ), patch("homeassistant.components.avea.avea.Bulb", return_value=mock_bulb), ): mock_config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() mock_bulb.connect.assert_called_once() mock_bulb.get_manufacturer_name.assert_called_once() mock_bulb.get_hardware_revision.assert_called_once() mock_bulb.get_fw_version.assert_called_once() mock_bulb.get_serial_number.assert_called_once() mock_bulb.disconnect.assert_not_called() device = device_registry.async_get_device_by_connection( (dr.CONNECTION_BLUETOOTH, AVEA_DISCOVERY_INFO.address), mock_config_entry.entry_id, ) assert device is not None assert device.manufacturer == "Elgato Systems GmbH" assert device.model == "Avea" assert device.hw_version == "Elgato Avea" assert device.sw_version == AVEA_FIRMWARE_VERSION assert device.serial_number == AVEA_SERIAL_NUMBER async def test_device_info_ignores_unknown_values( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_config_entry: MockConfigEntry, mock_bulb: MagicMock, ) -> None: """Test unknown device info is not populated.""" mock_bulb.get_manufacturer_name.return_value = UNKNOWN_NAME mock_bulb.get_hardware_revision.return_value = "" mock_bulb.get_fw_version.return_value = UNKNOWN_NAME mock_bulb.get_serial_number.return_value = "" with ( patch( "homeassistant.components.avea.async_ble_device_from_address", return_value=AVEA_DISCOVERY_INFO.device, ), patch("homeassistant.components.avea.avea.Bulb", return_value=mock_bulb), ): mock_config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() device = device_registry.async_get_device_by_connection( (dr.CONNECTION_BLUETOOTH, AVEA_DISCOVERY_INFO.address), mock_config_entry.entry_id, ) assert device is not None assert device.manufacturer is None assert device.model == "Avea" assert device.hw_version is None assert device.sw_version is None assert device.serial_number is None async def test_device_info_is_read_once( hass: HomeAssistant, setup_integration: MagicMock, freezer: FrozenDateTimeFactory, ) -> None: """Test device info is read once.""" bulb = setup_integration bulb.get_manufacturer_name.reset_mock() bulb.get_hardware_revision.reset_mock() bulb.get_fw_version.reset_mock() bulb.get_serial_number.reset_mock() freezer.tick(timedelta(seconds=30)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) bulb.get_manufacturer_name.assert_not_called() bulb.get_hardware_revision.assert_not_called() bulb.get_fw_version.assert_not_called() bulb.get_serial_number.assert_not_called() async def test_turn_on_and_off( hass: HomeAssistant, setup_integration: MagicMock, ) -> None: """Test turning the light on and off.""" bulb = setup_integration await hass.services.async_call( "light", "turn_on", {ATTR_ENTITY_ID: "light.bedroom"}, blocking=True, ) bulb.set_brightness.assert_called_with(4095) bulb.set_brightness.reset_mock() await hass.services.async_call( "light", "turn_on", {ATTR_ENTITY_ID: "light.bedroom", ATTR_BRIGHTNESS: 128}, blocking=True, ) bulb.set_brightness.assert_called_with(2056) bulb.set_rgb.reset_mock() await hass.services.async_call( "light", "turn_on", {ATTR_ENTITY_ID: "light.bedroom", ATTR_HS_COLOR: (0, 100)}, blocking=True, ) bulb.set_rgb.assert_called_with(255, 0, 0) bulb.set_brightness.reset_mock() await hass.services.async_call( "light", "turn_off", {ATTR_ENTITY_ID: "light.bedroom"}, blocking=True, ) bulb.set_brightness.assert_called_with(0) async def test_turn_on_restores_last_brightness( hass: HomeAssistant, setup_integration: MagicMock, freezer: FrozenDateTimeFactory, ) -> None: """Test turning the light on restores the last brightness.""" bulb = setup_integration bulb.get_brightness.side_effect = [3212, None, None, None] freezer.tick(timedelta(seconds=30)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("light.bedroom") assert state is not None assert state.attributes[LightEntityStateAttribute.BRIGHTNESS] == 200 bulb.set_brightness.reset_mock() await hass.services.async_call( "light", "turn_on", {ATTR_ENTITY_ID: "light.bedroom", ATTR_BRIGHTNESS: 10}, blocking=True, ) bulb.set_brightness.assert_called_with(161) bulb.set_brightness.reset_mock() await hass.services.async_call( "light", "turn_off", {ATTR_ENTITY_ID: "light.bedroom"}, blocking=True, ) bulb.set_brightness.assert_called_with(0) state = hass.states.get("light.bedroom") assert state is not None assert state.state == STATE_OFF assert state.attributes[LightEntityStateAttribute.BRIGHTNESS] is None bulb.set_brightness.reset_mock() await hass.services.async_call( "light", "turn_on", {ATTR_ENTITY_ID: "light.bedroom"}, blocking=True, ) bulb.set_brightness.assert_called_with(161) async def test_update_state( hass: HomeAssistant, setup_integration: MagicMock, freezer: FrozenDateTimeFactory ) -> None: """Test updating the entity state.""" state = hass.states.get("light.bedroom") assert state is not None assert state.state == STATE_OFF assert state.attributes[LightEntityStateAttribute.BRIGHTNESS] is None bulb = setup_integration bulb.reset_mock() bulb.connect.return_value = True bulb.get_brightness.return_value = 2048 bulb.get_rgb.return_value = (0, 255, 0) freezer.tick(timedelta(seconds=30)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) bulb.connect.assert_called_once() bulb.get_brightness.assert_called_once() bulb.get_rgb.assert_called_once() bulb.disconnect.assert_called_once() bulb.assert_has_calls( [ call.connect(), call.get_brightness(), call.get_rgb(), call.disconnect(), ] ) state = hass.states.get("light.bedroom") assert state is not None assert state.state == STATE_ON assert state.attributes[LightEntityStateAttribute.BRIGHTNESS] == 128 assert state.attributes[LightEntityStateAttribute.HS_COLOR] == (120.0, 100.0) async def test_update_state_uses_cached_values_when_connect_fails( hass: HomeAssistant, setup_integration: MagicMock, freezer: FrozenDateTimeFactory ) -> None: """Test updating the entity state when the shared connection fails.""" bulb = setup_integration bulb.reset_mock() bulb.connect.return_value = False bulb.get_brightness.return_value = 2048 bulb.get_rgb.return_value = (0, 255, 0) freezer.tick(timedelta(seconds=30)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) bulb.connect.assert_called_once() bulb.get_brightness.assert_called_once() bulb.get_rgb.assert_called_once() bulb.disconnect.assert_not_called() state = hass.states.get("light.bedroom") assert state is not None assert state.state == STATE_ON assert state.attributes[LightEntityStateAttribute.BRIGHTNESS] == 128 assert state.attributes[LightEntityStateAttribute.HS_COLOR] == (120.0, 100.0)