diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 3c9cd07a146..857d2d4b49f 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -47,6 +47,9 @@ from homeassistant.helpers import condition, extract_domain_configs import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import ( ATTR_CUR, @@ -227,6 +230,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up all automations.""" hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + # To register the automation blueprints async_get_blueprints(hass) diff --git a/homeassistant/components/automation/recorder.py b/homeassistant/components/automation/recorder.py new file mode 100644 index 00000000000..3083d271d1f --- /dev/null +++ b/homeassistant/components/automation/recorder.py @@ -0,0 +1,12 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.core import HomeAssistant, callback + +from . import ATTR_CUR, ATTR_LAST_TRIGGERED, ATTR_MAX, ATTR_MODE, CONF_ID + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude extra attributes from being recorded in the database.""" + return {ATTR_LAST_TRIGGERED, ATTR_MODE, ATTR_CUR, ATTR_MAX, CONF_ID} diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 2b9c9976ce4..2a950c749ac 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -32,6 +32,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import ( ATTR_CUR, @@ -165,6 +168,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Load the scripts from the configuration.""" hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + # To register scripts as valid domain for Blueprint async_get_blueprints(hass) diff --git a/homeassistant/components/script/recorder.py b/homeassistant/components/script/recorder.py new file mode 100644 index 00000000000..b1afc318b51 --- /dev/null +++ b/homeassistant/components/script/recorder.py @@ -0,0 +1,12 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.core import HomeAssistant, callback + +from . import ATTR_CUR, ATTR_LAST_ACTION, ATTR_LAST_TRIGGERED, ATTR_MAX, ATTR_MODE + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude extra attributes from being recorded in the database.""" + return {ATTR_LAST_TRIGGERED, ATTR_MODE, ATTR_CUR, ATTR_MAX, ATTR_LAST_ACTION} diff --git a/tests/components/automation/test_recorder.py b/tests/components/automation/test_recorder.py new file mode 100644 index 00000000000..3070452d345 --- /dev/null +++ b/tests/components/automation/test_recorder.py @@ -0,0 +1,68 @@ +"""The tests for automation recorder.""" +from __future__ import annotations + +import pytest + +from homeassistant.components import automation +from homeassistant.components.automation import ( + ATTR_CUR, + ATTR_LAST_TRIGGERED, + ATTR_MAX, + ATTR_MODE, + CONF_ID, +) +from homeassistant.components.recorder.models import StateAttributes, States +from homeassistant.components.recorder.util import session_scope +from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME +from homeassistant.core import State +from homeassistant.setup import async_setup_component + +from tests.common import async_init_recorder_component, async_mock_service +from tests.components.recorder.common import async_wait_recording_done_without_instance + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_exclude_attributes(hass, calls): + """Test automation registered attributes to be excluded.""" + await async_init_recorder_component(hass) + await hass.async_block_till_done() + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation", "entity_id": "hello.world"}, + } + }, + ) + await hass.async_block_till_done() + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 1 + assert ["hello.world"] == calls[0].data.get(ATTR_ENTITY_ID) + await async_wait_recording_done_without_instance(hass) + + def _fetch_states() -> list[State]: + with session_scope(hass=hass) as session: + native_states = [] + for db_state, db_state_attributes in session.query(States, StateAttributes): + state = db_state.to_native() + state.attributes = db_state_attributes.to_native() + native_states.append(state) + return native_states + + states: list[State] = await hass.async_add_executor_job(_fetch_states) + assert len(states) > 1 + for state in states: + assert ATTR_LAST_TRIGGERED not in state.attributes + assert ATTR_MODE not in state.attributes + assert ATTR_CUR not in state.attributes + assert CONF_ID not in state.attributes + assert ATTR_MAX not in state.attributes + assert ATTR_FRIENDLY_NAME in state.attributes diff --git a/tests/components/script/test_recorder.py b/tests/components/script/test_recorder.py new file mode 100644 index 00000000000..c9a4f97491e --- /dev/null +++ b/tests/components/script/test_recorder.py @@ -0,0 +1,83 @@ +"""The tests for script recorder.""" +from __future__ import annotations + +import pytest + +from homeassistant.components import script +from homeassistant.components.recorder.models import StateAttributes, States +from homeassistant.components.recorder.util import session_scope +from homeassistant.components.script import ( + ATTR_CUR, + ATTR_LAST_ACTION, + ATTR_LAST_TRIGGERED, + ATTR_MAX, + ATTR_MODE, +) +from homeassistant.const import ATTR_FRIENDLY_NAME +from homeassistant.core import Context, State, callback +from homeassistant.setup import async_setup_component + +from tests.common import async_init_recorder_component, async_mock_service +from tests.components.recorder.common import async_wait_recording_done_without_instance + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_exclude_attributes(hass, calls): + """Test automation registered attributes to be excluded.""" + await async_init_recorder_component(hass) + await hass.async_block_till_done() + calls = [] + context = Context() + + @callback + def record_call(service): + """Add recorded event to set.""" + calls.append(service) + + hass.services.async_register("test", "script", record_call) + + assert await async_setup_component( + hass, + "script", + { + "script": { + "test": { + "sequence": { + "service": "test.script", + "data_template": {"hello": "{{ greeting }}"}, + } + } + } + }, + ) + + await hass.services.async_call( + script.DOMAIN, "test", {"greeting": "world"}, context=context + ) + await hass.async_block_till_done() + await async_wait_recording_done_without_instance(hass) + assert len(calls) == 1 + + def _fetch_states() -> list[State]: + with session_scope(hass=hass) as session: + native_states = [] + for db_state, db_state_attributes in session.query(States, StateAttributes): + state = db_state.to_native() + state.attributes = db_state_attributes.to_native() + native_states.append(state) + return native_states + + states: list[State] = await hass.async_add_executor_job(_fetch_states) + assert len(states) > 1 + for state in states: + assert ATTR_LAST_TRIGGERED not in state.attributes + assert ATTR_MODE not in state.attributes + assert ATTR_CUR not in state.attributes + assert ATTR_LAST_ACTION not in state.attributes + assert ATTR_MAX not in state.attributes + assert ATTR_FRIENDLY_NAME in state.attributes