diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index e91aa687b21..e693d2ed814 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -166,7 +166,8 @@ async def async_setup(hass, config): # To register the automation blueprints async_get_blueprints(hass) - await _async_process_config(hass, config, component) + if not await _async_process_config(hass, config, component): + await async_get_blueprints(hass).async_populate() async def trigger_service_handler(entity, service_call): """Handle automation triggers.""" @@ -487,12 +488,13 @@ async def _async_process_config( hass: HomeAssistant, config: Dict[str, Any], component: EntityComponent, -) -> None: +) -> bool: """Process config and add automations. - This method is a coroutine. + Returns if blueprints were used. """ entities = [] + blueprints_used = False for config_key in extract_domain_configs(config, DOMAIN): conf: List[Union[Dict[str, Any], blueprint.BlueprintInputs]] = config[ # type: ignore @@ -501,6 +503,7 @@ async def _async_process_config( for list_no, config_block in enumerate(conf): if isinstance(config_block, blueprint.BlueprintInputs): # type: ignore + blueprints_used = True blueprint_inputs = config_block try: @@ -562,6 +565,8 @@ async def _async_process_config( if entities: await component.async_add_entities(entities) + return blueprints_used + async def _async_process_if(hass, config, p_config): """Process if checks.""" diff --git a/homeassistant/components/automation/blueprints/motion_light.yaml b/homeassistant/components/automation/blueprints/motion_light.yaml new file mode 100644 index 00000000000..aa787e3b2b5 --- /dev/null +++ b/homeassistant/components/automation/blueprints/motion_light.yaml @@ -0,0 +1,39 @@ +blueprint: + name: Motion-activated Light + domain: automation + source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/motion_light.yaml + input: + motion_entity: + name: Motion Sensor + selector: + entity: + domain: binary_sensor + device_class: motion + light_entity: + name: Light + selector: + entity: + domain: light + +# If motion is detected within the 120s delay, +# we restart the script. +mode: restart +max_exceeded: silent + +trigger: + platform: state + entity_id: !placeholder motion_entity + from: "off" + to: "on" + +action: + - service: homeassistant.turn_on + entity_id: !placeholder light_entity + - wait_for_trigger: + platform: state + entity_id: !placeholder motion_entity + from: "on" + to: "off" + - delay: 120 + - service: homeassistant.turn_off + entity_id: !placeholder light_entity diff --git a/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml b/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml new file mode 100644 index 00000000000..7e7dba8bea8 --- /dev/null +++ b/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml @@ -0,0 +1,34 @@ +blueprint: + name: Send notification when a person leaves a zone + domain: automation + source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml + input: + person_entity: + name: Person + selector: + entity: + domain: person + zone_entity: + name: Zone + selector: + entity: + domain: zone + notify_service: + name: The notify service to use + +trigger: + platform: state + entity_id: !placeholder person_entity + +variables: + zone_entity: !placeholder zone_entity + zone_state: "{{ states[zone_entity].name }}" + +condition: + condition: template + value_template: "{{ trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}" + +action: + - service: !placeholder notify_service + data: + message: "{{ trigger.to_state.name }} has left {{ zone_state }}" diff --git a/homeassistant/components/blueprint/models.py b/homeassistant/components/blueprint/models.py index 48390d0bd23..e19439133ee 100644 --- a/homeassistant/components/blueprint/models.py +++ b/homeassistant/components/blueprint/models.py @@ -2,14 +2,16 @@ import asyncio import logging import pathlib +import shutil from typing import Any, Dict, List, Optional, Union from pkg_resources import parse_version import voluptuous as vol from voluptuous.humanize import humanize_error +from homeassistant import loader from homeassistant.const import CONF_DOMAIN, CONF_NAME, CONF_PATH, __version__ -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import placeholder from homeassistant.util import yaml @@ -169,6 +171,11 @@ class DomainBlueprints: hass.data.setdefault(DOMAIN, {})[domain] = self + @property + def blueprint_folder(self) -> pathlib.Path: + """Return the blueprint folder.""" + return pathlib.Path(self.hass.config.path(BLUEPRINT_FOLDER, self.domain)) + @callback def async_reset_cache(self) -> None: """Reset the blueprint cache.""" @@ -177,9 +184,7 @@ class DomainBlueprints: def _load_blueprint(self, blueprint_path) -> Blueprint: """Load a blueprint.""" try: - blueprint_data = yaml.load_yaml( - self.hass.config.path(BLUEPRINT_FOLDER, self.domain, blueprint_path) - ) + blueprint_data = yaml.load_yaml(self.blueprint_folder / blueprint_path) except (HomeAssistantError, FileNotFoundError) as err: raise FailedToLoad(self.domain, blueprint_path, err) from err @@ -257,10 +262,7 @@ class DomainBlueprints: async def async_remove_blueprint(self, blueprint_path: str) -> None: """Remove a blueprint file.""" - path = pathlib.Path( - self.hass.config.path(BLUEPRINT_FOLDER, self.domain, blueprint_path) - ) - + path = self.blueprint_folder / blueprint_path await self.hass.async_add_executor_job(path.unlink) self._blueprints[blueprint_path] = None @@ -288,3 +290,18 @@ class DomainBlueprints: ) self._blueprints[blueprint_path] = blueprint + + async def async_populate(self) -> None: + """Create folder if it doesn't exist and populate with examples.""" + integration = await loader.async_get_integration(self.hass, self.domain) + + def populate(): + if self.blueprint_folder.exists(): + return + + shutil.copytree( + integration.file_path / BLUEPRINT_FOLDER, + self.blueprint_folder / HA_DOMAIN, + ) + + await self.hass.async_add_executor_job(populate) diff --git a/homeassistant/components/blueprint/schemas.py b/homeassistant/components/blueprint/schemas.py index ed78b4d2b42..900497c3f71 100644 --- a/homeassistant/components/blueprint/schemas.py +++ b/homeassistant/components/blueprint/schemas.py @@ -63,6 +63,7 @@ BLUEPRINT_SCHEMA = vol.Schema( vol.Required(CONF_BLUEPRINT): vol.Schema( { vol.Required(CONF_NAME): str, + vol.Optional(CONF_DESCRIPTION): str, vol.Required(CONF_DOMAIN): str, vol.Optional(CONF_SOURCE_URL): cv.url, vol.Optional(CONF_HOMEASSISTANT): { diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 9cfa6c00996..5ace4c91bcf 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -453,18 +453,12 @@ def async_template( ) -> bool: """Test if template condition matches.""" try: - value = value_template.async_render(variables) + value: str = value_template.async_render(variables, parse_result=False) except TemplateError as ex: _LOGGER.error("Error during template condition: %s", ex) return False - if isinstance(value, bool): - return value - - if isinstance(value, str): - return value.lower() == "true" - - return False + return value.lower() == "true" def async_template_from_config( diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 5bf70f01bfa..d3e96a5205e 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -23,6 +23,10 @@ def validate_selector(config: Any) -> Dict: if selector_class is None: raise vol.Invalid(f"Unknown selector type {selector_type} found") + # Seletors can be empty + if config[selector_type] is None: + return {selector_type: {}} + return { selector_type: cast(Dict, selector_class.CONFIG_SCHEMA(config[selector_type])) } @@ -44,6 +48,8 @@ class EntitySelector(Selector): vol.Optional("integration"): str, # Domain the entity belongs to vol.Optional("domain"): str, + # Device class of the entity + vol.Optional("device_class"): str, } ) diff --git a/tests/common.py b/tests/common.py index f0994308fe6..dbe2cbfd42a 100644 --- a/tests/common.py +++ b/tests/common.py @@ -9,6 +9,7 @@ from io import StringIO import json import logging import os +import pathlib import threading import time import uuid @@ -704,6 +705,9 @@ def patch_yaml_files(files_dict, endswith=True): def mock_open_f(fname, **_): """Mock open() in the yaml module, used by load_yaml.""" # Return the mocked file on full match + if isinstance(fname, pathlib.Path): + fname = str(fname) + if fname in files_dict: _LOGGER.debug("patch_yaml_files match %s", fname) res = StringIO(files_dict[fname]) diff --git a/tests/components/alarm_control_panel/test_device_action.py b/tests/components/alarm_control_panel/test_device_action.py index 74c98da3189..514e8fa81f2 100644 --- a/tests/components/alarm_control_panel/test_device_action.py +++ b/tests/components/alarm_control_panel/test_device_action.py @@ -23,6 +23,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/alarm_control_panel/test_device_condition.py b/tests/components/alarm_control_panel/test_device_condition.py index fcb2ba5a09b..33f717e1893 100644 --- a/tests/components/alarm_control_panel/test_device_condition.py +++ b/tests/components/alarm_control_panel/test_device_condition.py @@ -22,6 +22,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/alarm_control_panel/test_device_trigger.py b/tests/components/alarm_control_panel/test_device_trigger.py index 9d414d179ab..82432bc37ab 100644 --- a/tests/components/alarm_control_panel/test_device_trigger.py +++ b/tests/components/alarm_control_panel/test_device_trigger.py @@ -22,6 +22,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/arcam_fmj/test_device_trigger.py b/tests/components/arcam_fmj/test_device_trigger.py index 2a119fd2017..0f2cfaf2893 100644 --- a/tests/components/arcam_fmj/test_device_trigger.py +++ b/tests/components/arcam_fmj/test_device_trigger.py @@ -13,6 +13,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/automation/conftest.py b/tests/components/automation/conftest.py new file mode 100644 index 00000000000..a967e0af192 --- /dev/null +++ b/tests/components/automation/conftest.py @@ -0,0 +1,3 @@ +"""Conftest for automation tests.""" + +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa diff --git a/tests/components/automation/test_blueprint.py b/tests/components/automation/test_blueprint.py new file mode 100644 index 00000000000..a601998a00d --- /dev/null +++ b/tests/components/automation/test_blueprint.py @@ -0,0 +1,185 @@ +"""Test built-in blueprints.""" +import asyncio +import contextlib +from datetime import timedelta +import pathlib + +from homeassistant.components import automation +from homeassistant.components.blueprint import models +from homeassistant.core import callback +from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util, yaml + +from tests.async_mock import patch +from tests.common import async_fire_time_changed, async_mock_service + +BUILTIN_BLUEPRINT_FOLDER = pathlib.Path(automation.__file__).parent / "blueprints" + + +@contextlib.contextmanager +def patch_blueprint(blueprint_path: str, data_path): + """Patch blueprint loading from a different source.""" + orig_load = models.DomainBlueprints._load_blueprint + + @callback + def mock_load_blueprint(self, path): + if path != blueprint_path: + assert False, f"Unexpected blueprint {path}" + return orig_load(self, path) + + return models.Blueprint( + yaml.load_yaml(data_path), expected_domain=self.domain, path=path + ) + + with patch( + "homeassistant.components.blueprint.models.DomainBlueprints._load_blueprint", + mock_load_blueprint, + ): + yield + + +async def test_notify_leaving_zone(hass): + """Test notifying leaving a zone blueprint.""" + + def set_person_state(state, extra={}): + hass.states.async_set( + "person.test_person", state, {"friendly_name": "Paulus", **extra} + ) + + set_person_state("School") + + assert await async_setup_component( + hass, "zone", {"zone": {"name": "School", "latitude": 1, "longitude": 2}} + ) + + with patch_blueprint( + "notify_leaving_zone.yaml", + BUILTIN_BLUEPRINT_FOLDER / "notify_leaving_zone.yaml", + ): + assert await async_setup_component( + hass, + "automation", + { + "automation": { + "use_blueprint": { + "path": "notify_leaving_zone.yaml", + "input": { + "person_entity": "person.test_person", + "zone_entity": "zone.school", + "notify_service": "notify.test_service", + }, + } + } + }, + ) + + calls = async_mock_service(hass, "notify", "test_service") + + # Leaving zone to no zone + set_person_state("not_home") + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].data["message"] == "Paulus has left School" + + # Should not increase when we go to another zone + set_person_state("bla") + await hass.async_block_till_done() + + assert len(calls) == 1 + + # Should not increase when we go into the zone + set_person_state("School") + await hass.async_block_till_done() + + assert len(calls) == 1 + + # Should not increase when we move in the zone + set_person_state("School", {"extra_key": "triggers change with same state"}) + await hass.async_block_till_done() + + assert len(calls) == 1 + + # Should increase when leaving zone for another zone + set_person_state("Just Outside School") + await hass.async_block_till_done() + + assert len(calls) == 2 + + +async def test_motion_light(hass): + """Test motion light blueprint.""" + hass.states.async_set("binary_sensor.kitchen", "off") + + with patch_blueprint( + "motion_light.yaml", + BUILTIN_BLUEPRINT_FOLDER / "motion_light.yaml", + ): + assert await async_setup_component( + hass, + "automation", + { + "automation": { + "use_blueprint": { + "path": "motion_light.yaml", + "input": { + "light_entity": "light.kitchen", + "motion_entity": "binary_sensor.kitchen", + }, + } + } + }, + ) + + turn_on_calls = async_mock_service(hass, "homeassistant", "turn_on") + turn_off_calls = async_mock_service(hass, "homeassistant", "turn_off") + + # Turn on motion + hass.states.async_set("binary_sensor.kitchen", "on") + # Can't block till done because delay is active + # So wait 5 event loop iterations to process script + for _ in range(5): + await asyncio.sleep(0) + + assert len(turn_on_calls) == 1 + + # Test light doesn't turn off if motion stays + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200)) + + for _ in range(5): + await asyncio.sleep(0) + + assert len(turn_off_calls) == 0 + + # Test light turns off off 120s after last motion + hass.states.async_set("binary_sensor.kitchen", "off") + + for _ in range(5): + await asyncio.sleep(0) + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=120)) + await hass.async_block_till_done() + + assert len(turn_off_calls) == 1 + + # Test restarting the script + hass.states.async_set("binary_sensor.kitchen", "on") + + for _ in range(5): + await asyncio.sleep(0) + + assert len(turn_on_calls) == 2 + assert len(turn_off_calls) == 1 + + hass.states.async_set("binary_sensor.kitchen", "off") + + for _ in range(5): + await asyncio.sleep(0) + + hass.states.async_set("binary_sensor.kitchen", "on") + + for _ in range(15): + await asyncio.sleep(0) + + assert len(turn_on_calls) == 3 + assert len(turn_off_calls) == 1 diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index 9fd50277d23..7a7e80e36a3 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -20,6 +20,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index cea0103acdb..fef109eb9d5 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -20,6 +20,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/blueprint/conftest.py b/tests/components/blueprint/conftest.py new file mode 100644 index 00000000000..c8110ddaf08 --- /dev/null +++ b/tests/components/blueprint/conftest.py @@ -0,0 +1,14 @@ +"""Blueprints conftest.""" + +import pytest + +from tests.async_mock import patch + + +@pytest.fixture(autouse=True) +def stub_blueprint_populate(): + """Stub copying the blueprint automations to the config folder.""" + with patch( + "homeassistant.components.blueprint.models.DomainBlueprints.async_populate" + ): + yield diff --git a/tests/components/blueprint/test_default_blueprints.py b/tests/components/blueprint/test_default_blueprints.py new file mode 100644 index 00000000000..5e4016fcb88 --- /dev/null +++ b/tests/components/blueprint/test_default_blueprints.py @@ -0,0 +1,28 @@ +"""Test default blueprints.""" +import importlib +import logging +import pathlib + +import pytest + +from homeassistant.components.blueprint import models +from homeassistant.components.blueprint.const import BLUEPRINT_FOLDER +from homeassistant.util import yaml + +DOMAINS = ["automation"] +LOGGER = logging.getLogger(__name__) + + +@pytest.mark.parametrize("domain", DOMAINS) +def test_default_blueprints(domain: str): + """Validate a folder of blueprints.""" + integration = importlib.import_module(f"homeassistant.components.{domain}") + blueprint_folder = pathlib.Path(integration.__file__).parent / BLUEPRINT_FOLDER + items = list(blueprint_folder.glob("*")) + assert len(items) > 0, "Folder cannot be empty" + + for fil in items: + LOGGER.info("Processing %s", fil) + assert fil.name.endswith(".yaml") + data = yaml.load_yaml(fil) + models.Blueprint(data, expected_domain=domain) diff --git a/tests/components/climate/test_device_action.py b/tests/components/climate/test_device_action.py index ff78b837591..4084d37358e 100644 --- a/tests/components/climate/test_device_action.py +++ b/tests/components/climate/test_device_action.py @@ -15,6 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index 431849ae761..8e6d5829c41 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -15,6 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/climate/test_device_trigger.py b/tests/components/climate/test_device_trigger.py index 58aa3311771..69bb4626e49 100644 --- a/tests/components/climate/test_device_trigger.py +++ b/tests/components/climate/test_device_trigger.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 1d160870169..347ac96f892 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -5,6 +5,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import config from tests.async_mock import patch +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa async def test_get_device_config(hass, hass_client): diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index 1f82434c7a6..1e1cbccf60a 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -4,6 +4,7 @@ import pytest from homeassistant.components.config import device_registry from tests.common import mock_device_registry +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/cover/test_device_action.py b/tests/components/cover/test_device_action.py index d302353582c..cad6074ff34 100644 --- a/tests/components/cover/test_device_action.py +++ b/tests/components/cover/test_device_action.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/cover/test_device_condition.py b/tests/components/cover/test_device_condition.py index 511c7ced898..b3098ceeca9 100644 --- a/tests/components/cover/test_device_condition.py +++ b/tests/components/cover/test_device_condition.py @@ -22,6 +22,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index 1996cf9d6df..ab054ad8223 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -22,6 +22,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index 69c9ae94727..a5399fe4796 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -19,6 +19,7 @@ from homeassistant.const import ( from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration from tests.common import assert_lists_same, async_get_device_automations +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa SENSORS = { "1": { diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py index 6bf9cc44c56..a4a5898982b 100644 --- a/tests/components/default_config/test_init.py +++ b/tests/components/default_config/test_init.py @@ -4,6 +4,7 @@ import pytest from homeassistant.setup import async_setup_component from tests.async_mock import patch +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture(autouse=True) diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index 19786bb08e8..83ec146b53e 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -13,6 +13,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/device_tracker/test_device_condition.py b/tests/components/device_tracker/test_device_condition.py index 950ace24335..a187f21e954 100644 --- a/tests/components/device_tracker/test_device_condition.py +++ b/tests/components/device_tracker/test_device_condition.py @@ -15,6 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/device_tracker/test_device_trigger.py b/tests/components/device_tracker/test_device_trigger.py index 963dae3127d..2f0ec14ec4b 100644 --- a/tests/components/device_tracker/test_device_trigger.py +++ b/tests/components/device_tracker/test_device_trigger.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa AWAY_LATITUDE = 32.881011 AWAY_LONGITUDE = -117.234758 diff --git a/tests/components/fan/test_device_action.py b/tests/components/fan/test_device_action.py index 70da4bd1fca..d3a9aedcf9c 100644 --- a/tests/components/fan/test_device_action.py +++ b/tests/components/fan/test_device_action.py @@ -14,6 +14,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/fan/test_device_condition.py b/tests/components/fan/test_device_condition.py index 939fee154c5..6725587aeda 100644 --- a/tests/components/fan/test_device_condition.py +++ b/tests/components/fan/test_device_condition.py @@ -15,6 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/fan/test_device_trigger.py b/tests/components/fan/test_device_trigger.py index c46b3a6fcec..d96f0a828f3 100644 --- a/tests/components/fan/test_device_trigger.py +++ b/tests/components/fan/test_device_trigger.py @@ -15,6 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/geo_location/test_trigger.py b/tests/components/geo_location/test_trigger.py index 8bf1c6abe15..ab984a2c309 100644 --- a/tests/components/geo_location/test_trigger.py +++ b/tests/components/geo_location/test_trigger.py @@ -7,6 +7,7 @@ from homeassistant.core import Context from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_component +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/homeassistant/triggers/conftest.py b/tests/components/homeassistant/triggers/conftest.py new file mode 100644 index 00000000000..5c983ba698e --- /dev/null +++ b/tests/components/homeassistant/triggers/conftest.py @@ -0,0 +1,3 @@ +"""Conftest for HA triggers.""" + +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa diff --git a/tests/components/homekit_controller/test_device_trigger.py b/tests/components/homekit_controller/test_device_trigger.py index c8ef2cbef38..9de9a30f99f 100644 --- a/tests/components/homekit_controller/test_device_trigger.py +++ b/tests/components/homekit_controller/test_device_trigger.py @@ -12,6 +12,7 @@ from tests.common import ( async_get_device_automations, async_mock_service, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa from tests.components.homekit_controller.common import setup_test_component diff --git a/tests/components/hue/test_device_trigger.py b/tests/components/hue/test_device_trigger.py index b6d3f4f2f50..0975c644e61 100644 --- a/tests/components/hue/test_device_trigger.py +++ b/tests/components/hue/test_device_trigger.py @@ -15,6 +15,7 @@ from tests.common import ( async_mock_service, mock_device_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa REMOTES_RESPONSE = {"7": HUE_TAP_REMOTE_1, "8": HUE_DIMMER_REMOTE_1} diff --git a/tests/components/humidifier/test_device_action.py b/tests/components/humidifier/test_device_action.py index 91b7819e18b..93b97408c39 100644 --- a/tests/components/humidifier/test_device_action.py +++ b/tests/components/humidifier/test_device_action.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index 76a850887ca..ad001d52ae0 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -17,6 +17,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/humidifier/test_device_trigger.py b/tests/components/humidifier/test_device_trigger.py index 4f93b4be4de..7cd736b79f4 100644 --- a/tests/components/humidifier/test_device_trigger.py +++ b/tests/components/humidifier/test_device_trigger.py @@ -20,6 +20,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/kodi/test_device_trigger.py b/tests/components/kodi/test_device_trigger.py index de2d69212ef..8cf6c635393 100644 --- a/tests/components/kodi/test_device_trigger.py +++ b/tests/components/kodi/test_device_trigger.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/light/test_device_action.py b/tests/components/light/test_device_action.py index aeedde82af5..63670d9bfab 100644 --- a/tests/components/light/test_device_action.py +++ b/tests/components/light/test_device_action.py @@ -21,6 +21,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 2a43a0abebe..eea443b7d34 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -19,6 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/light/test_device_trigger.py b/tests/components/light/test_device_trigger.py index d3c630cd0dc..fad39898467 100644 --- a/tests/components/light/test_device_trigger.py +++ b/tests/components/light/test_device_trigger.py @@ -19,6 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/litejet/test_trigger.py b/tests/components/litejet/test_trigger.py index b8699785d5e..3cbbd474b88 100644 --- a/tests/components/litejet/test_trigger.py +++ b/tests/components/litejet/test_trigger.py @@ -11,6 +11,7 @@ import homeassistant.components.automation as automation import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, async_mock_service +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/lock/test_device_action.py b/tests/components/lock/test_device_action.py index dbf390df57b..91cab9cdaf4 100644 --- a/tests/components/lock/test_device_action.py +++ b/tests/components/lock/test_device_action.py @@ -15,6 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/lock/test_device_condition.py b/tests/components/lock/test_device_condition.py index c2db984f16f..949100daa55 100644 --- a/tests/components/lock/test_device_condition.py +++ b/tests/components/lock/test_device_condition.py @@ -15,6 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/lock/test_device_trigger.py b/tests/components/lock/test_device_trigger.py index 006df742c6d..20674c483fd 100644 --- a/tests/components/lock/test_device_trigger.py +++ b/tests/components/lock/test_device_trigger.py @@ -15,6 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/media_player/test_device_condition.py b/tests/components/media_player/test_device_condition.py index c52daa80320..c7668d748af 100644 --- a/tests/components/media_player/test_device_condition.py +++ b/tests/components/media_player/test_device_condition.py @@ -21,6 +21,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index 844f34e8d4f..a46406c330f 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index b35082ef2c6..a26ecadb6bd 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -7,6 +7,7 @@ from homeassistant.setup import async_setup_component from tests.async_mock import ANY from tests.common import async_fire_mqtt_message, async_mock_service, mock_component +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/remote/test_device_action.py b/tests/components/remote/test_device_action.py index 89d0f0de6bf..17165639e25 100644 --- a/tests/components/remote/test_device_action.py +++ b/tests/components/remote/test_device_action.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/remote/test_device_condition.py b/tests/components/remote/test_device_condition.py index 2f01b4ab55f..e5a9bc3a9c9 100644 --- a/tests/components/remote/test_device_condition.py +++ b/tests/components/remote/test_device_condition.py @@ -19,6 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/remote/test_device_trigger.py b/tests/components/remote/test_device_trigger.py index 73feb3ea08f..eccf96c04f6 100644 --- a/tests/components/remote/test_device_trigger.py +++ b/tests/components/remote/test_device_trigger.py @@ -19,6 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index a379b91f82a..5710fa04698 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -3,6 +3,7 @@ from homeassistant.components import search from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa async def test_search(hass): diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index 0c0c9c6c22b..9a023d6f5ad 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index dda57de0d9d..c39b4597632 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -20,6 +20,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES diff --git a/tests/components/sun/test_trigger.py b/tests/components/sun/test_trigger.py index 2cb21051200..1a3de56964c 100644 --- a/tests/components/sun/test_trigger.py +++ b/tests/components/sun/test_trigger.py @@ -18,6 +18,7 @@ import homeassistant.util.dt as dt_util from tests.async_mock import patch from tests.common import async_fire_time_changed, async_mock_service, mock_component +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa ORIG_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/components/switch/test_device_action.py b/tests/components/switch/test_device_action.py index 204b2370cb8..4da401a215c 100644 --- a/tests/components/switch/test_device_action.py +++ b/tests/components/switch/test_device_action.py @@ -16,6 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/switch/test_device_condition.py b/tests/components/switch/test_device_condition.py index 6e542ee24d1..093758cbe62 100644 --- a/tests/components/switch/test_device_condition.py +++ b/tests/components/switch/test_device_condition.py @@ -19,6 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/switch/test_device_trigger.py b/tests/components/switch/test_device_trigger.py index 512942ca71b..34817b687f8 100644 --- a/tests/components/switch/test_device_trigger.py +++ b/tests/components/switch/test_device_trigger.py @@ -19,6 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/tag/test_trigger.py b/tests/components/tag/test_trigger.py index 0249acaf29b..3a83c8e5d2b 100644 --- a/tests/components/tag/test_trigger.py +++ b/tests/components/tag/test_trigger.py @@ -8,6 +8,7 @@ from homeassistant.components.tag.const import DOMAIN, TAG_ID from homeassistant.setup import async_setup_component from tests.common import async_mock_service +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/tasmota/test_device_trigger.py b/tests/components/tasmota/test_device_trigger.py index 09c3d691b09..42fc5dc7a49 100644 --- a/tests/components/tasmota/test_device_trigger.py +++ b/tests/components/tasmota/test_device_trigger.py @@ -18,6 +18,7 @@ from tests.common import ( async_fire_mqtt_message, async_get_device_automations, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa async def test_get_triggers(hass, device_reg, entity_reg, mqtt_mock, setup_tasmota): diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index 4bda4dc23ca..828cf1fb7b4 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -17,6 +17,7 @@ from tests.common import ( async_mock_service, mock_component, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/vacuum/test_device_action.py b/tests/components/vacuum/test_device_action.py index 47ce5423f7d..3edeaba2a41 100644 --- a/tests/components/vacuum/test_device_action.py +++ b/tests/components/vacuum/test_device_action.py @@ -14,6 +14,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/vacuum/test_device_condition.py b/tests/components/vacuum/test_device_condition.py index 16715266b8c..3dc7a628741 100644 --- a/tests/components/vacuum/test_device_condition.py +++ b/tests/components/vacuum/test_device_condition.py @@ -19,6 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/vacuum/test_device_trigger.py b/tests/components/vacuum/test_device_trigger.py index f3439700e33..e3f615891e6 100644 --- a/tests/components/vacuum/test_device_trigger.py +++ b/tests/components/vacuum/test_device_trigger.py @@ -14,6 +14,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/water_heater/test_device_action.py b/tests/components/water_heater/test_device_action.py index 3d3e70444b7..06bd43ec654 100644 --- a/tests/components/water_heater/test_device_action.py +++ b/tests/components/water_heater/test_device_action.py @@ -14,6 +14,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/components/webhook/test_trigger.py b/tests/components/webhook/test_trigger.py index bb3cb680743..46459bd88c4 100644 --- a/tests/components/webhook/test_trigger.py +++ b/tests/components/webhook/test_trigger.py @@ -5,6 +5,7 @@ from homeassistant.core import callback from homeassistant.setup import async_setup_component from tests.async_mock import patch +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture(autouse=True) diff --git a/tests/components/zha/test_device_action.py b/tests/components/zha/test_device_action.py index de3a4eb1296..c0350ce63a5 100644 --- a/tests/components/zha/test_device_action.py +++ b/tests/components/zha/test_device_action.py @@ -16,6 +16,7 @@ from homeassistant.helpers.device_registry import async_get_registry from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_coro +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa SHORT_PRESS = "remote_button_short_press" COMMAND = "command" diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index 801b6831379..b72f693e531 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -19,6 +19,7 @@ from tests.common import ( async_get_device_automations, async_mock_service, ) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa ON = 1 OFF = 0 diff --git a/tests/components/zone/test_trigger.py b/tests/components/zone/test_trigger.py index 0477f9bead7..d7f5857b466 100644 --- a/tests/components/zone/test_trigger.py +++ b/tests/components/zone/test_trigger.py @@ -7,6 +7,7 @@ from homeassistant.core import Context from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_component +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @pytest.fixture diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index 786fa986a14..1c25dbedcde 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -165,4 +165,4 @@ action: } with patch("os.path.isfile", return_value=True), patch_yaml_files(files): res = await async_check_ha_config_file(hass) - assert len(res["automation"]) == 1 + assert len(res.get("automation", [])) == 1 diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 51490567d8e..a8410821c42 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -6,7 +6,25 @@ from homeassistant.helpers import selector @pytest.mark.parametrize( - "schema", ({}, {"non_existing": {}}, {"device": {}, "entity": {}}) + "schema", + ( + {"device": None}, + {"entity": None}, + ), +) +def test_valid_base_schema(schema): + """Test base schema validation.""" + selector.validate_selector(schema) + + +@pytest.mark.parametrize( + "schema", + ( + {}, + {"non_existing": {}}, + # Two keys + {"device": {}, "entity": {}}, + ), ) def test_invalid_base_schema(schema): """Test base schema validation."""