From 1f0f62de7f1ffdaf460d7969e543e8c8688fecb6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 26 Jan 2020 23:01:35 -0800 Subject: [PATCH] Add unique IDs to automation/scenes (#31150) * Add unique IDs to automation and scenes * Fix typo --- .../components/automation/__init__.py | 5 +++ homeassistant/components/config/__init__.py | 8 +++- homeassistant/components/config/automation.py | 18 +++++++-- homeassistant/components/config/customize.py | 2 +- homeassistant/components/config/group.py | 2 +- homeassistant/components/config/scene.py | 19 ++++++++-- homeassistant/components/config/script.py | 2 +- .../components/homeassistant/scene.py | 5 +++ homeassistant/components/scene/__init__.py | 5 +-- tests/components/config/test_automation.py | 38 ++++++++++++++++--- tests/components/config/test_customize.py | 6 ++- tests/components/config/test_group.py | 1 + tests/components/config/test_scene.py | 29 +++++++++++--- tests/components/scene/test_init.py | 10 ++++- 14 files changed, 123 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 70b8b26fa2c..52769063b7e 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -189,6 +189,11 @@ class AutomationEntity(ToggleEntity, RestoreEntity): """Name of the automation.""" return self._name + @property + def unique_id(self): + """Return unique ID.""" + return self._id + @property def should_poll(self): """No polling needed for automation entities.""" diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index 5873cdc3271..ad7ae14ecb7 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -28,6 +28,8 @@ SECTIONS = ( "scene", ) ON_DEMAND = ("zwave",) +ACTION_CREATE_UPDATE = "create_update" +ACTION_DELETE = "delete" async def async_setup(hass, config): @@ -152,7 +154,9 @@ class BaseEditConfigView(HomeAssistantView): await hass.async_add_executor_job(_write, path, current) if self.post_write_hook is not None: - hass.async_create_task(self.post_write_hook(hass)) + hass.async_create_task( + self.post_write_hook(ACTION_CREATE_UPDATE, config_key) + ) return self.json({"result": "ok"}) @@ -170,7 +174,7 @@ class BaseEditConfigView(HomeAssistantView): await hass.async_add_executor_job(_write, path, current) if self.post_write_hook is not None: - hass.async_create_task(self.post_write_hook(hass)) + hass.async_create_task(self.post_write_hook(ACTION_DELETE, config_key)) return self.json({"result": "ok"}) diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index d7bb1ef9883..6216a52fc13 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -6,18 +6,30 @@ from homeassistant.components.automation import DOMAIN, PLATFORM_SCHEMA from homeassistant.components.automation.config import async_validate_config_item from homeassistant.config import AUTOMATION_CONFIG_PATH from homeassistant.const import CONF_ID, SERVICE_RELOAD -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry -from . import EditIdBasedConfigView +from . import ACTION_DELETE, EditIdBasedConfigView async def async_setup(hass): """Set up the Automation config API.""" - async def hook(hass): + async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) + if action != ACTION_DELETE: + return + + ent_reg = await entity_registry.async_get_registry(hass) + + entity_id = ent_reg.async_get_entity_id(DOMAIN, DOMAIN, config_key) + + if entity_id is None: + return + + ent_reg.async_remove(entity_id) + hass.http.register_view( EditAutomationConfigView( DOMAIN, diff --git a/homeassistant/components/config/customize.py b/homeassistant/components/config/customize.py index ed75a8a04a6..3b1122fc3a5 100644 --- a/homeassistant/components/config/customize.py +++ b/homeassistant/components/config/customize.py @@ -12,7 +12,7 @@ CONFIG_PATH = "customize.yaml" async def async_setup(hass): """Set up the Customize config API.""" - async def hook(hass): + async def hook(action, config_key): """post_write_hook for Config View that reloads groups.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD_CORE_CONFIG) diff --git a/homeassistant/components/config/group.py b/homeassistant/components/config/group.py index d95891af655..e26b2b80bc1 100644 --- a/homeassistant/components/config/group.py +++ b/homeassistant/components/config/group.py @@ -10,7 +10,7 @@ from . import EditKeyBasedConfigView async def async_setup(hass): """Set up the Group config API.""" - async def hook(hass): + async def hook(action, config_key): """post_write_hook for Config View that reloads groups.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) diff --git a/homeassistant/components/config/scene.py b/homeassistant/components/config/scene.py index 79a30177e47..b380656c541 100644 --- a/homeassistant/components/config/scene.py +++ b/homeassistant/components/config/scene.py @@ -5,18 +5,31 @@ import uuid from homeassistant.components.scene import DOMAIN, PLATFORM_SCHEMA from homeassistant.config import SCENE_CONFIG_PATH from homeassistant.const import CONF_ID, SERVICE_RELOAD -import homeassistant.helpers.config_validation as cv +from homeassistant.core import DOMAIN as HA_DOMAIN +from homeassistant.helpers import config_validation as cv, entity_registry -from . import EditIdBasedConfigView +from . import ACTION_DELETE, EditIdBasedConfigView async def async_setup(hass): """Set up the Scene config API.""" - async def hook(hass): + async def hook(action, config_key): """post_write_hook for Config View that reloads scenes.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) + if action != ACTION_DELETE: + return + + ent_reg = await entity_registry.async_get_registry(hass) + + entity_id = ent_reg.async_get_entity_id(DOMAIN, HA_DOMAIN, config_key) + + if entity_id is None: + return + + ent_reg.async_remove(entity_id) + hass.http.register_view( EditSceneConfigView( DOMAIN, diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index 032774de473..de9c25b223f 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -10,7 +10,7 @@ from . import EditKeyBasedConfigView async def async_setup(hass): """Set up the script config API.""" - async def hook(hass): + async def hook(action, config_key): """post_write_hook for Config View that reloads scripts.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index a142c787506..af5f4cea828 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -261,6 +261,11 @@ class HomeAssistantScene(Scene): """Return the name of the scene.""" return self.scene_config.name + @property + def unique_id(self): + """Return unique ID.""" + return self._id + @property def device_state_attributes(self): """Return the scene state attributes.""" diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 75ec2bfd875..8b530e1e728 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -61,10 +61,7 @@ async def async_setup(hass, config): await component.async_setup(config) # Ensure Home Assistant platform always loaded. - await component.async_setup_platform( - HA_DOMAIN, {"platform": "homeasistant", STATES: []} - ) - + await component.async_setup_platform(HA_DOMAIN, {"platform": HA_DOMAIN, STATES: []}) component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_activate") return True diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index b345a219d3f..45ffa1d08ec 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -1,6 +1,7 @@ """Test Automation config panel.""" import json -from unittest.mock import patch + +from asynctest import patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import config @@ -47,7 +48,7 @@ async def test_update_device_config(hass, hass_client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write - ): + ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): resp = await client.post( "/api/config/automation/config/moon", data=json.dumps({"trigger": [], "action": [], "condition": []}), @@ -89,11 +90,12 @@ async def test_bad_formatted_automations(hass, hass_client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write - ): + ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): resp = await client.post( "/api/config/automation/config/moon", data=json.dumps({"trigger": [], "action": [], "condition": []}), ) + await hass.async_block_till_done() assert resp.status == 200 result = await resp.json() @@ -107,8 +109,31 @@ async def test_bad_formatted_automations(hass, hass_client): async def test_delete_automation(hass, hass_client): """Test deleting an automation.""" + ent_reg = await hass.helpers.entity_registry.async_get_registry() + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + }, + { + "id": "moon", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + }, + ] + }, + ) + + assert len(ent_reg.entities) == 2 + with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + assert await async_setup_component(hass, "config", {}) client = await hass_client() @@ -126,8 +151,9 @@ async def test_delete_automation(hass, hass_client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write - ): + ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): resp = await client.delete("/api/config/automation/config/sun") + await hass.async_block_till_done() assert resp.status == 200 result = await resp.json() @@ -135,3 +161,5 @@ async def test_delete_automation(hass, hass_client): assert len(written) == 1 assert written[0][0]["id"] == "moon" + + assert len(ent_reg.entities) == 1 diff --git a/tests/components/config/test_customize.py b/tests/components/config/test_customize.py index 45c1f40d4ad..d8c9ea19b70 100644 --- a/tests/components/config/test_customize.py +++ b/tests/components/config/test_customize.py @@ -1,6 +1,7 @@ """Test Customize config panel.""" import json -from unittest.mock import patch + +from asynctest import patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import config @@ -53,6 +54,8 @@ async def test_update_entity(hass, hass_client): hass.states.async_set("hello.world", "state", {"a": "b"}) with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write + ), patch( + "homeassistant.config.async_hass_config_yaml", return_value={}, ): resp = await client.post( "/api/config/customize/config/hello.world", @@ -60,6 +63,7 @@ async def test_update_entity(hass, hass_client): {"name": "Beer", "entities": ["light.top", "light.bottom"]} ), ) + await hass.async_block_till_done() assert resp.status == 200 result = await resp.json() diff --git a/tests/components/config/test_group.py b/tests/components/config/test_group.py index 1b79f30a5b6..49d168e2796 100644 --- a/tests/components/config/test_group.py +++ b/tests/components/config/test_group.py @@ -61,6 +61,7 @@ async def test_update_device_config(hass, hass_client): {"name": "Beer", "entities": ["light.top", "light.bottom"]} ), ) + await hass.async_block_till_done() assert resp.status == 200 result = await resp.json() diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index b40c895b620..b51628f87ae 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -1,6 +1,7 @@ """Test Automation config panel.""" import json -from unittest.mock import patch + +from asynctest import patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import config @@ -29,7 +30,7 @@ async def test_update_scene(hass, hass_client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write - ): + ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): resp = await client.post( "/api/config/scene/config/light_off", data=json.dumps( @@ -86,7 +87,7 @@ async def test_bad_formatted_scene(hass, hass_client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write - ): + ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): resp = await client.post( "/api/config/scene/config/light_off", data=json.dumps( @@ -114,8 +115,23 @@ async def test_bad_formatted_scene(hass, hass_client): async def test_delete_scene(hass, hass_client): """Test deleting a scene.""" + ent_reg = await hass.helpers.entity_registry.async_get_registry() + + assert await async_setup_component( + hass, + "scene", + { + "scene": [ + {"id": "light_on", "name": "Light on", "entities": {}}, + {"id": "light_off", "name": "Light off", "entities": {}}, + ] + }, + ) + + assert len(ent_reg.entities) == 2 + with patch.object(config, "SECTIONS", ["scene"]): - await async_setup_component(hass, "config", {}) + assert await async_setup_component(hass, "config", {}) client = await hass_client() @@ -133,8 +149,9 @@ async def test_delete_scene(hass, hass_client): with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write - ): + ), patch("homeassistant.config.async_hass_config_yaml", return_value={}): resp = await client.delete("/api/config/scene/config/light_on") + await hass.async_block_till_done() assert resp.status == 200 result = await resp.json() @@ -142,3 +159,5 @@ async def test_delete_scene(hass, hass_client): assert len(written) == 1 assert written[0][0]["id"] == "light_off" + + assert len(ent_reg.entities) == 1 diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index f26189eec6c..8211ff10857 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -3,7 +3,7 @@ import io import unittest from homeassistant.components import light, scene -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component, setup_component from homeassistant.util.yaml import loader as yaml_loader from tests.common import get_test_home_assistant @@ -128,3 +128,11 @@ class TestScene(unittest.TestCase): assert self.light_1.is_on assert self.light_2.is_on assert 100 == self.light_2.last_call("turn_on")[1].get("brightness") + + +async def test_services_registered(hass): + """Test we register services with empty config.""" + assert await async_setup_component(hass, "scene", {}) + assert hass.services.has_service("scene", "reload") + assert hass.services.has_service("scene", "turn_on") + assert hass.services.has_service("scene", "apply")