diff --git a/tests/components/wled/snapshots/test_number.ambr b/tests/components/wled/snapshots/test_number.ambr new file mode 100644 index 00000000000..96b465616c4 --- /dev/null +++ b/tests/components/wled/snapshots/test_number.ambr @@ -0,0 +1,335 @@ +# serializer version: 1 +# name: test_numbers[number.wled_rgb_light_segment_1_intensity-42-intensity] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'WLED RGB Light Segment 1 Intensity', + 'max': 255, + 'min': 0, + 'mode': , + 'step': 1, + }), + 'context': , + 'entity_id': 'number.wled_rgb_light_segment_1_intensity', + 'last_changed': , + 'last_updated': , + 'state': '64', + }) +# --- +# name: test_numbers[number.wled_rgb_light_segment_1_intensity-42-intensity].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 255, + 'min': 0, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.wled_rgb_light_segment_1_intensity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Segment 1 Intensity', + 'platform': 'wled', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_intensity_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_numbers[number.wled_rgb_light_segment_1_intensity-42-intensity].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': 'http://127.0.0.1', + 'connections': set({ + tuple( + 'mac', + 'aa:bb:cc:dd:ee:ff', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': 'esp8266', + 'id': , + 'identifiers': set({ + tuple( + 'wled', + 'aabbccddeeff', + ), + }), + 'is_new': False, + 'manufacturer': 'WLED', + 'model': 'DIY light', + 'name': 'WLED RGB Light', + 'name_by_user': None, + 'suggested_area': None, + 'sw_version': '0.8.5', + 'via_device_id': None, + }) +# --- +# name: test_numbers[number.wled_rgb_light_segment_1_speed-42-speed] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'WLED RGB Light Segment 1 Speed', + 'icon': 'mdi:speedometer', + 'max': 255, + 'min': 0, + 'mode': , + 'step': 1, + }), + 'context': , + 'entity_id': 'number.wled_rgb_light_segment_1_speed', + 'last_changed': , + 'last_updated': , + 'state': '16', + }) +# --- +# name: test_numbers[number.wled_rgb_light_segment_1_speed-42-speed].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 255, + 'min': 0, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.wled_rgb_light_segment_1_speed', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:speedometer', + 'original_name': 'Segment 1 Speed', + 'platform': 'wled', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_speed_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_numbers[number.wled_rgb_light_segment_1_speed-42-speed].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': 'http://127.0.0.1', + 'connections': set({ + tuple( + 'mac', + 'aa:bb:cc:dd:ee:ff', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': 'esp8266', + 'id': , + 'identifiers': set({ + tuple( + 'wled', + 'aabbccddeeff', + ), + }), + 'is_new': False, + 'manufacturer': 'WLED', + 'model': 'DIY light', + 'name': 'WLED RGB Light', + 'name_by_user': None, + 'suggested_area': None, + 'sw_version': '0.8.5', + 'via_device_id': None, + }) +# --- +# name: test_speed_state[number.wled_rgb_light_segment_1_intensity-42-intensity] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'WLED RGB Light Segment 1 Intensity', + 'max': 255, + 'min': 0, + 'mode': , + 'step': 1, + }), + 'context': , + 'entity_id': 'number.wled_rgb_light_segment_1_intensity', + 'last_changed': , + 'last_updated': , + 'state': '64', + }) +# --- +# name: test_speed_state[number.wled_rgb_light_segment_1_intensity-42-intensity].1 + EntityRegistryEntrySnapshot({ + '_display_repr': , + '_partial_repr': , + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 255, + 'min': 0, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.wled_rgb_light_segment_1_intensity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Segment 1 Intensity', + 'platform': 'wled', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_intensity_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_speed_state[number.wled_rgb_light_segment_1_intensity-42-intensity].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': 'http://127.0.0.1', + 'connections': set({ + tuple( + 'mac', + 'aa:bb:cc:dd:ee:ff', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': 'esp8266', + 'id': , + 'identifiers': set({ + tuple( + 'wled', + 'aabbccddeeff', + ), + }), + 'is_new': False, + 'manufacturer': 'WLED', + 'model': 'DIY light', + 'name': 'WLED RGB Light', + 'name_by_user': None, + 'suggested_area': None, + 'sw_version': '0.8.5', + 'via_device_id': None, + }) +# --- +# name: test_speed_state[number.wled_rgb_light_segment_1_speed-42-speed] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'WLED RGB Light Segment 1 Speed', + 'icon': 'mdi:speedometer', + 'max': 255, + 'min': 0, + 'mode': , + 'step': 1, + }), + 'context': , + 'entity_id': 'number.wled_rgb_light_segment_1_speed', + 'last_changed': , + 'last_updated': , + 'state': '16', + }) +# --- +# name: test_speed_state[number.wled_rgb_light_segment_1_speed-42-speed].1 + EntityRegistryEntrySnapshot({ + '_display_repr': , + '_partial_repr': , + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 255, + 'min': 0, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.wled_rgb_light_segment_1_speed', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:speedometer', + 'original_name': 'Segment 1 Speed', + 'platform': 'wled', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_speed_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_speed_state[number.wled_rgb_light_segment_1_speed-42-speed].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': 'http://127.0.0.1', + 'connections': set({ + tuple( + 'mac', + 'aa:bb:cc:dd:ee:ff', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': 'esp8266', + 'id': , + 'identifiers': set({ + tuple( + 'wled', + 'aabbccddeeff', + ), + }), + 'is_new': False, + 'manufacturer': 'WLED', + 'model': 'DIY light', + 'name': 'WLED RGB Light', + 'name_by_user': None, + 'suggested_area': None, + 'sw_version': '0.8.5', + 'via_device_id': None, + }) +# --- diff --git a/tests/components/wled/test_number.py b/tests/components/wled/test_number.py index 150db495155..59f2fb12332 100644 --- a/tests/components/wled/test_number.py +++ b/tests/components/wled/test_number.py @@ -3,21 +3,19 @@ import json from unittest.mock import MagicMock import pytest +from syrupy.assertion import SnapshotAssertion from wled import Device as WLEDDevice, WLEDConnectionError, WLEDError from homeassistant.components.number import ( - ATTR_MAX, - ATTR_MIN, - ATTR_STEP, ATTR_VALUE, DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE, ) from homeassistant.components.wled.const import SCAN_INTERVAL -from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON, STATE_UNAVAILABLE +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, load_fixture @@ -25,52 +23,106 @@ from tests.common import async_fire_time_changed, load_fixture pytestmark = pytest.mark.usefixtures("init_integration") -async def test_speed_state( - hass: HomeAssistant, entity_registry: er.EntityRegistry +@pytest.mark.parametrize( + ("entity_id", "value", "called_arg"), + [ + ("number.wled_rgb_light_segment_1_speed", 42, "speed"), + ("number.wled_rgb_light_segment_1_intensity", 42, "intensity"), + ], +) +async def test_numbers( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_wled: MagicMock, + entity_id: str, + value: int, + called_arg: str, ) -> None: """Test the creation and values of the WLED numbers.""" - # First segment of the strip - assert (state := hass.states.get("number.wled_rgb_light_segment_1_speed")) - assert state.attributes.get(ATTR_ICON) == "mdi:speedometer" - assert state.attributes.get(ATTR_MAX) == 255 - assert state.attributes.get(ATTR_MIN) == 0 - assert state.attributes.get(ATTR_STEP) == 1 - assert state.state == "16" + assert (state := hass.states.get(entity_id)) + assert state == snapshot - assert (entry := entity_registry.async_get("number.wled_rgb_light_segment_1_speed")) - assert entry.unique_id == "aabbccddeeff_speed_1" + assert (entity_entry := entity_registry.async_get(state.entity_id)) + assert entity_entry == snapshot + assert entity_entry.device_id + assert (device_entry := device_registry.async_get(entity_entry.device_id)) + assert device_entry == snapshot -async def test_speed_segment_change_state( - hass: HomeAssistant, - mock_wled: MagicMock, -) -> None: - """Test the value change of the WLED segments.""" + # Test a regular state change service call await hass.services.async_call( NUMBER_DOMAIN, SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_speed", - ATTR_VALUE: 42, - }, + {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: value}, blocking=True, ) + assert mock_wled.segment.call_count == 1 - mock_wled.segment.assert_called_with( - segment_id=1, - speed=42, - ) + mock_wled.segment.assert_called_with(segment_id=1, **{called_arg: value}) + + # Test with WLED error + mock_wled.segment.side_effect = WLEDError + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: value}, + blocking=True, + ) + assert mock_wled.segment.call_count == 2 + + # Ensure the entity is still available + assert (state := hass.states.get(entity_id)) + assert state.state != STATE_UNAVAILABLE + + # Test when a connection error occurs + mock_wled.segment.side_effect = WLEDConnectionError + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: value}, + blocking=True, + ) + assert mock_wled.segment.call_count == 3 + + # Ensure the entity became unavailable after the connection error + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_UNAVAILABLE @pytest.mark.parametrize("device_fixture", ["rgb_single_segment"]) +@pytest.mark.parametrize( + ("entity_id_segment0", "state_segment0", "entity_id_segment1", "state_segment1"), + [ + ( + "number.wled_rgb_light_speed", + "32", + "number.wled_rgb_light_segment_1_speed", + "16", + ), + ( + "number.wled_rgb_light_intensity", + "128", + "number.wled_rgb_light_segment_1_intensity", + "64", + ), + ], +) async def test_speed_dynamically_handle_segments( hass: HomeAssistant, mock_wled: MagicMock, + entity_id_segment0: str, + entity_id_segment1: str, + state_segment0: str, + state_segment1: str, ) -> None: """Test if a new/deleted segment is dynamically added/removed.""" - assert (segment0 := hass.states.get("number.wled_rgb_light_speed")) - assert segment0.state == "32" - assert not hass.states.get("number.wled_rgb_light_segment_1_speed") + assert (segment0 := hass.states.get(entity_id_segment0)) + assert segment0.state == state_segment0 + assert not hass.states.get(entity_id_segment1) # Test adding a segment dynamically... return_value = mock_wled.update.return_value @@ -81,187 +133,17 @@ async def test_speed_dynamically_handle_segments( async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert (segment0 := hass.states.get("number.wled_rgb_light_speed")) - assert segment0.state == "32" - assert (segment1 := hass.states.get("number.wled_rgb_light_segment_1_speed")) - assert segment1.state == "16" + assert (segment0 := hass.states.get(entity_id_segment0)) + assert segment0.state == state_segment0 + assert (segment1 := hass.states.get(entity_id_segment1)) + assert segment1.state == state_segment1 # Test remove segment again... mock_wled.update.return_value = return_value async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert (segment0 := hass.states.get("number.wled_rgb_light_speed")) - assert segment0.state == "32" - assert (segment1 := hass.states.get("number.wled_rgb_light_segment_1_speed")) + assert (segment0 := hass.states.get(entity_id_segment0)) + assert segment0.state == state_segment0 + assert (segment1 := hass.states.get(entity_id_segment1)) assert segment1.state == STATE_UNAVAILABLE - - -async def test_speed_error( - hass: HomeAssistant, - mock_wled: MagicMock, -) -> None: - """Test error handling of the WLED numbers.""" - mock_wled.segment.side_effect = WLEDError - - with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_speed", - ATTR_VALUE: 42, - }, - blocking=True, - ) - - assert (state := hass.states.get("number.wled_rgb_light_segment_1_speed")) - assert state.state == "16" - assert mock_wled.segment.call_count == 1 - mock_wled.segment.assert_called_with(segment_id=1, speed=42) - - -async def test_speed_connection_error( - hass: HomeAssistant, - mock_wled: MagicMock, -) -> None: - """Test error handling of the WLED numbers.""" - mock_wled.segment.side_effect = WLEDConnectionError - - with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_speed", - ATTR_VALUE: 42, - }, - blocking=True, - ) - - assert (state := hass.states.get("number.wled_rgb_light_segment_1_speed")) - assert state.state == STATE_UNAVAILABLE - assert mock_wled.segment.call_count == 1 - mock_wled.segment.assert_called_with(segment_id=1, speed=42) - - -async def test_intensity_state( - hass: HomeAssistant, entity_registry: er.EntityRegistry -) -> None: - """Test the creation and values of the WLED numbers.""" - # First segment of the strip - assert (state := hass.states.get("number.wled_rgb_light_segment_1_intensity")) - assert state.attributes.get(ATTR_ICON) is None - assert state.attributes.get(ATTR_MAX) == 255 - assert state.attributes.get(ATTR_MIN) == 0 - assert state.attributes.get(ATTR_STEP) == 1 - assert state.state == "64" - - assert ( - entry := entity_registry.async_get("number.wled_rgb_light_segment_1_intensity") - ) - assert entry.unique_id == "aabbccddeeff_intensity_1" - - -async def test_intensity_segment_change_state( - hass: HomeAssistant, - mock_wled: MagicMock, -) -> None: - """Test the value change of the WLED segments.""" - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_intensity", - ATTR_VALUE: 128, - }, - blocking=True, - ) - assert mock_wled.segment.call_count == 1 - mock_wled.segment.assert_called_with( - segment_id=1, - intensity=128, - ) - - -@pytest.mark.parametrize("device_fixture", ["rgb_single_segment"]) -async def test_intensity_dynamically_handle_segments( - hass: HomeAssistant, - mock_wled: MagicMock, -) -> None: - """Test if a new/deleted segment is dynamically added/removed.""" - assert (segment0 := hass.states.get("number.wled_rgb_light_intensity")) - assert segment0.state == "128" - assert not hass.states.get("number.wled_rgb_light_segment_1_intensity") - - # Test adding a segment dynamically... - return_value = mock_wled.update.return_value - mock_wled.update.return_value = WLEDDevice( - json.loads(load_fixture("wled/rgb.json")) - ) - - async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() - - assert (segment0 := hass.states.get("number.wled_rgb_light_intensity")) - assert segment0.state == "128" - assert (segment1 := hass.states.get("number.wled_rgb_light_segment_1_intensity")) - assert segment1.state == "64" - - # Test remove segment again... - mock_wled.update.return_value = return_value - async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() - - assert (segment0 := hass.states.get("number.wled_rgb_light_intensity")) - assert segment0.state == "128" - assert (segment1 := hass.states.get("number.wled_rgb_light_segment_1_intensity")) - assert segment1.state == STATE_UNAVAILABLE - - -async def test_intensity_error( - hass: HomeAssistant, - mock_wled: MagicMock, -) -> None: - """Test error handling of the WLED numbers.""" - mock_wled.segment.side_effect = WLEDError - - with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_intensity", - ATTR_VALUE: 21, - }, - blocking=True, - ) - - assert (state := hass.states.get("number.wled_rgb_light_segment_1_intensity")) - assert state.state == "64" - assert mock_wled.segment.call_count == 1 - mock_wled.segment.assert_called_with(segment_id=1, intensity=21) - - -async def test_intensity_connection_error( - hass: HomeAssistant, - mock_wled: MagicMock, -) -> None: - """Test error handling of the WLED numbers.""" - mock_wled.segment.side_effect = WLEDConnectionError - - with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_intensity", - ATTR_VALUE: 128, - }, - blocking=True, - ) - - assert (state := hass.states.get("number.wled_rgb_light_segment_1_intensity")) - assert state.state == STATE_UNAVAILABLE - assert mock_wled.segment.call_count == 1 - mock_wled.segment.assert_called_with(segment_id=1, intensity=128)