From c2457b8574c8c3e590b9580158d44794b7132d7b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 27 Jun 2023 08:20:05 +0200 Subject: [PATCH] Use entity registry id in cover device actions (#95269) --- .../components/cover/device_action.py | 16 +- .../components/device_automation/helpers.py | 1 + tests/components/cover/test_device_action.py | 169 +++++++++++++++--- 3 files changed, 154 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index dd22821d5e4..e34a623be93 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -3,6 +3,7 @@ from __future__ import annotations import voluptuous as vol +from homeassistant.components.device_automation import async_validate_entity_schema from homeassistant.const import ( ATTR_ENTITY_ID, CONF_DEVICE_ID, @@ -42,21 +43,28 @@ POSITION_ACTION_TYPES = {"set_position", "set_tilt_position"} CMD_ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( { vol.Required(CONF_TYPE): vol.In(CMD_ACTION_TYPES), - vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), + vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid, } ) POSITION_ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( { vol.Required(CONF_TYPE): vol.In(POSITION_ACTION_TYPES), - vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), + vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid, vol.Optional("position", default=0): vol.All( vol.Coerce(int), vol.Range(min=0, max=100) ), } ) -ACTION_SCHEMA = vol.Any(CMD_ACTION_SCHEMA, POSITION_ACTION_SCHEMA) +_ACTION_SCHEMA = vol.Any(CMD_ACTION_SCHEMA, POSITION_ACTION_SCHEMA) + + +async def async_validate_action_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: + """Validate config.""" + return async_validate_entity_schema(hass, config, _ACTION_SCHEMA) async def async_get_actions( @@ -77,7 +85,7 @@ async def async_get_actions( base_action = { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, - CONF_ENTITY_ID: entry.entity_id, + CONF_ENTITY_ID: entry.id, } if supported_features & SUPPORT_SET_POSITION: diff --git a/homeassistant/components/device_automation/helpers.py b/homeassistant/components/device_automation/helpers.py index 1d727b598a0..8a7e80cc560 100644 --- a/homeassistant/components/device_automation/helpers.py +++ b/homeassistant/components/device_automation/helpers.py @@ -28,6 +28,7 @@ STATIC_VALIDATOR = { ENTITY_PLATFORMS = { Platform.ALARM_CONTROL_PANEL.value, Platform.BUTTON.value, + Platform.COVER.value, Platform.FAN.value, Platform.HUMIDIFIER.value, Platform.LIGHT.value, diff --git a/tests/components/cover/test_device_action.py b/tests/components/cover/test_device_action.py index ac798e8b3d4..0cc6716bd3c 100644 --- a/tests/components/cover/test_device_action.py +++ b/tests/components/cover/test_device_action.py @@ -61,7 +61,7 @@ async def test_get_actions( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entity_registry.async_get_or_create( + entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", "5678", @@ -70,7 +70,7 @@ async def test_get_actions( ) if set_state: hass.states.async_set( - f"{DOMAIN}.test_5678", "attributes", {"supported_features": features_state} + entity_entry.entity_id, "attributes", {"supported_features": features_state} ) await hass.async_block_till_done() @@ -80,7 +80,7 @@ async def test_get_actions( "domain": DOMAIN, "type": action, "device_id": device_entry.id, - "entity_id": f"{DOMAIN}.test_5678", + "entity_id": entity_entry.id, "metadata": {"secondary": False}, } for action in expected_action_types @@ -114,7 +114,7 @@ async def test_get_actions_hidden_auxiliary( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entity_registry.async_get_or_create( + entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", "5678", @@ -129,7 +129,7 @@ async def test_get_actions_hidden_auxiliary( "domain": DOMAIN, "type": action, "device_id": device_entry.id, - "entity_id": f"{DOMAIN}.test_5678", + "entity_id": entity_entry.id, "metadata": {"secondary": True}, } for action in ["close"] @@ -190,6 +190,57 @@ async def test_get_action_capabilities( assert capabilities == {"extra_fields": []} +async def test_get_action_capabilities_legacy( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + enable_custom_integrations: None, +) -> None: + """Test we get the expected capabilities from a cover action.""" + platform = getattr(hass.components, f"test.{DOMAIN}") + platform.init(empty=True) + platform.ENTITIES.append( + platform.MockCover( + name="Set position cover", + is_on=True, + unique_id="unique_set_pos_cover", + current_cover_position=50, + supported_features=CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT, + ), + ) + ent = platform.ENTITIES[0] + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_registry.async_get_or_create( + DOMAIN, "test", ent.unique_id, device_id=device_entry.id + ) + + actions = await async_get_device_automations( + hass, DeviceAutomationType.ACTION, device_entry.id + ) + assert len(actions) == 5 # open, close, open_tilt, close_tilt + action_types = {action["type"] for action in actions} + assert action_types == {"open", "close", "stop", "open_tilt", "close_tilt"} + for action in actions: + action["entity_id"] = entity_registry.async_get(action["entity_id"]).entity_id + capabilities = await async_get_device_automation_capabilities( + hass, DeviceAutomationType.ACTION, action + ) + assert capabilities == {"extra_fields": []} + + async def test_get_action_capabilities_set_pos( hass: HomeAssistant, device_registry: dr.DeviceRegistry, @@ -298,11 +349,13 @@ async def test_get_action_capabilities_set_tilt_pos( assert capabilities == {"extra_fields": []} -async def test_action(hass: HomeAssistant, enable_custom_integrations: None) -> None: +async def test_action( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + enable_custom_integrations: None, +) -> None: """Test for cover actions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") assert await async_setup_component( hass, @@ -314,7 +367,7 @@ async def test_action(hass: HomeAssistant, enable_custom_integrations: None) -> "action": { "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "open", }, }, @@ -323,7 +376,7 @@ async def test_action(hass: HomeAssistant, enable_custom_integrations: None) -> "action": { "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "close", }, }, @@ -332,7 +385,7 @@ async def test_action(hass: HomeAssistant, enable_custom_integrations: None) -> "action": { "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "stop", }, }, @@ -363,14 +416,24 @@ async def test_action(hass: HomeAssistant, enable_custom_integrations: None) -> assert len(close_calls) == 1 assert len(stop_calls) == 1 + assert open_calls[0].domain == DOMAIN + assert open_calls[0].service == "open_cover" + assert open_calls[0].data == {"entity_id": entry.entity_id} + assert close_calls[0].domain == DOMAIN + assert close_calls[0].service == "close_cover" + assert close_calls[0].data == {"entity_id": entry.entity_id} + assert stop_calls[0].domain == DOMAIN + assert stop_calls[0].service == "stop_cover" + assert stop_calls[0].data == {"entity_id": entry.entity_id} -async def test_action_tilt( - hass: HomeAssistant, enable_custom_integrations: None + +async def test_action_legacy( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + enable_custom_integrations: None, ) -> None: - """Test for cover tilt actions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + """Test for cover actions.""" + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") assert await async_setup_component( hass, @@ -382,7 +445,45 @@ async def test_action_tilt( "action": { "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": "cover.entity", + "entity_id": entry.id, + "type": "open", + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + open_calls = async_mock_service(hass, "cover", "open_cover") + + hass.bus.async_fire("test_event_open") + await hass.async_block_till_done() + assert len(open_calls) == 1 + + assert open_calls[0].domain == DOMAIN + assert open_calls[0].service == "open_cover" + assert open_calls[0].data == {"entity_id": entry.entity_id} + + +async def test_action_tilt( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + enable_custom_integrations: None, +) -> None: + """Test for cover tilt actions.""" + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": {"platform": "event", "event_type": "test_event_open"}, + "action": { + "domain": DOMAIN, + "device_id": "abcdefgh", + "entity_id": entry.id, "type": "open_tilt", }, }, @@ -391,7 +492,7 @@ async def test_action_tilt( "action": { "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "close_tilt", }, }, @@ -418,14 +519,21 @@ async def test_action_tilt( assert len(open_calls) == 1 assert len(close_calls) == 1 + assert open_calls[0].domain == DOMAIN + assert open_calls[0].service == "open_cover_tilt" + assert open_calls[0].data == {"entity_id": entry.entity_id} + assert close_calls[0].domain == DOMAIN + assert close_calls[0].service == "close_cover_tilt" + assert close_calls[0].data == {"entity_id": entry.entity_id} + async def test_action_set_position( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + enable_custom_integrations: None, ) -> None: """Test for cover set position actions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") assert await async_setup_component( hass, @@ -440,7 +548,7 @@ async def test_action_set_position( "action": { "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "set_position", "position": 25, }, @@ -453,7 +561,7 @@ async def test_action_set_position( "action": { "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": "cover.entity", + "entity_id": entry.id, "type": "set_tilt_position", "position": 75, }, @@ -469,11 +577,16 @@ async def test_action_set_position( hass.bus.async_fire("test_event_set_pos") await hass.async_block_till_done() assert len(cover_pos_calls) == 1 - assert cover_pos_calls[0].data["position"] == 25 assert len(tilt_pos_calls) == 0 hass.bus.async_fire("test_event_set_tilt_pos") await hass.async_block_till_done() assert len(cover_pos_calls) == 1 assert len(tilt_pos_calls) == 1 - assert tilt_pos_calls[0].data["tilt_position"] == 75 + + assert cover_pos_calls[0].domain == DOMAIN + assert cover_pos_calls[0].service == "set_cover_position" + assert cover_pos_calls[0].data == {"entity_id": entry.entity_id, "position": 25} + assert tilt_pos_calls[0].domain == DOMAIN + assert tilt_pos_calls[0].service == "set_cover_tilt_position" + assert tilt_pos_calls[0].data == {"entity_id": entry.entity_id, "tilt_position": 75}