Refactor WLED number tests (#88582)

This commit is contained in:
Franck Nijhof 2023-03-06 01:49:01 +01:00 committed by GitHub
parent a0ff95cef8
commit ff485d4648
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 426 additions and 209 deletions

View file

@ -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': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.wled_rgb_light_segment_1_intensity',
'last_changed': <ANY>,
'last_updated': <ANY>,
'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': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.wled_rgb_light_segment_1_intensity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <ANY>,
'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': <ANY>,
'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': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.wled_rgb_light_segment_1_speed',
'last_changed': <ANY>,
'last_updated': <ANY>,
'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': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.wled_rgb_light_segment_1_speed',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <ANY>,
'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': <ANY>,
'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': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.wled_rgb_light_segment_1_intensity',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '64',
})
# ---
# name: test_speed_state[number.wled_rgb_light_segment_1_intensity-42-intensity].1
EntityRegistryEntrySnapshot({
'_display_repr': <UndefinedType._singleton: 0>,
'_partial_repr': <UndefinedType._singleton: 0>,
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 255,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.wled_rgb_light_segment_1_intensity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <ANY>,
'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': <ANY>,
'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': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.wled_rgb_light_segment_1_speed',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '16',
})
# ---
# name: test_speed_state[number.wled_rgb_light_segment_1_speed-42-speed].1
EntityRegistryEntrySnapshot({
'_display_repr': <UndefinedType._singleton: 0>,
'_partial_repr': <UndefinedType._singleton: 0>,
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 255,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.wled_rgb_light_segment_1_speed',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <ANY>,
'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': <ANY>,
'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,
})
# ---

View file

@ -3,21 +3,19 @@ import json
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
from wled import Device as WLEDDevice, WLEDConnectionError, WLEDError from wled import Device as WLEDDevice, WLEDConnectionError, WLEDError
from homeassistant.components.number import ( from homeassistant.components.number import (
ATTR_MAX,
ATTR_MIN,
ATTR_STEP,
ATTR_VALUE, ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN, DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,
) )
from homeassistant.components.wled.const import SCAN_INTERVAL 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.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError 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 import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed, load_fixture 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") pytestmark = pytest.mark.usefixtures("init_integration")
async def test_speed_state( @pytest.mark.parametrize(
hass: HomeAssistant, entity_registry: er.EntityRegistry ("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: ) -> None:
"""Test the creation and values of the WLED numbers.""" """Test the creation and values of the WLED numbers."""
# First segment of the strip assert (state := hass.states.get(entity_id))
assert (state := hass.states.get("number.wled_rgb_light_segment_1_speed")) assert state == snapshot
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 (entry := entity_registry.async_get("number.wled_rgb_light_segment_1_speed")) assert (entity_entry := entity_registry.async_get(state.entity_id))
assert entry.unique_id == "aabbccddeeff_speed_1" 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( # Test a regular state change service call
hass: HomeAssistant,
mock_wled: MagicMock,
) -> None:
"""Test the value change of the WLED segments."""
await hass.services.async_call( await hass.services.async_call(
NUMBER_DOMAIN, NUMBER_DOMAIN,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,
{ {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: value},
ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_speed",
ATTR_VALUE: 42,
},
blocking=True, blocking=True,
) )
assert mock_wled.segment.call_count == 1 assert mock_wled.segment.call_count == 1
mock_wled.segment.assert_called_with( mock_wled.segment.assert_called_with(segment_id=1, **{called_arg: value})
segment_id=1,
speed=42, # 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("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( async def test_speed_dynamically_handle_segments(
hass: HomeAssistant, hass: HomeAssistant,
mock_wled: MagicMock, mock_wled: MagicMock,
entity_id_segment0: str,
entity_id_segment1: str,
state_segment0: str,
state_segment1: str,
) -> None: ) -> None:
"""Test if a new/deleted segment is dynamically added/removed.""" """Test if a new/deleted segment is dynamically added/removed."""
assert (segment0 := hass.states.get("number.wled_rgb_light_speed")) assert (segment0 := hass.states.get(entity_id_segment0))
assert segment0.state == "32" assert segment0.state == state_segment0
assert not hass.states.get("number.wled_rgb_light_segment_1_speed") assert not hass.states.get(entity_id_segment1)
# Test adding a segment dynamically... # Test adding a segment dynamically...
return_value = mock_wled.update.return_value 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) async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done() await hass.async_block_till_done()
assert (segment0 := hass.states.get("number.wled_rgb_light_speed")) assert (segment0 := hass.states.get(entity_id_segment0))
assert segment0.state == "32" assert segment0.state == state_segment0
assert (segment1 := hass.states.get("number.wled_rgb_light_segment_1_speed")) assert (segment1 := hass.states.get(entity_id_segment1))
assert segment1.state == "16" assert segment1.state == state_segment1
# Test remove segment again... # Test remove segment again...
mock_wled.update.return_value = return_value mock_wled.update.return_value = return_value
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done() await hass.async_block_till_done()
assert (segment0 := hass.states.get("number.wled_rgb_light_speed")) assert (segment0 := hass.states.get(entity_id_segment0))
assert segment0.state == "32" assert segment0.state == state_segment0
assert (segment1 := hass.states.get("number.wled_rgb_light_segment_1_speed")) assert (segment1 := hass.states.get(entity_id_segment1))
assert segment1.state == STATE_UNAVAILABLE 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)