Allow exposing any entity to the default conversation agent (#92398)

* Allow exposing any entity to the default conversation agent

* Tweak

* Fix race, update tests

* Update tests
This commit is contained in:
Erik Montnemery 2023-05-03 15:45:54 +02:00 committed by GitHub
parent 6a8668effc
commit 0126cfa9d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 195 additions and 128 deletions

View file

@ -1,4 +1,5 @@
"""Const for conversation integration.""" """Const for conversation integration."""
DOMAIN = "conversation" DOMAIN = "conversation"
DEFAULT_EXPOSED_ATTRIBUTES = {"device_class"}
HOME_ASSISTANT_AGENT = "homeassistant" HOME_ASSISTANT_AGENT = "homeassistant"

View file

@ -21,19 +21,21 @@ from homeassistant.components.homeassistant.exposed_entities import (
async_listen_entity_updates, async_listen_entity_updates,
async_should_expose, async_should_expose,
) )
from homeassistant.const import ATTR_DEVICE_CLASS from homeassistant.const import MATCH_ALL
from homeassistant.helpers import ( from homeassistant.helpers import (
area_registry as ar, area_registry as ar,
device_registry as dr, device_registry as dr,
entity_registry as er, entity_registry as er,
intent, intent,
start,
template, template,
translation, translation,
) )
from homeassistant.helpers.event import async_track_state_change
from homeassistant.util.json import JsonObjectType, json_loads_object from homeassistant.util.json import JsonObjectType, json_loads_object
from .agent import AbstractConversationAgent, ConversationInput, ConversationResult from .agent import AbstractConversationAgent, ConversationInput, ConversationResult
from .const import DOMAIN from .const import DEFAULT_EXPOSED_ATTRIBUTES, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_DEFAULT_ERROR_TEXT = "Sorry, I couldn't understand that" _DEFAULT_ERROR_TEXT = "Sorry, I couldn't understand that"
@ -81,16 +83,24 @@ def async_setup(hass: core.HomeAssistant) -> None:
async_should_expose(hass, DOMAIN, entity_id) async_should_expose(hass, DOMAIN, entity_id)
@core.callback @core.callback
def async_handle_entity_registry_changed(event: core.Event) -> None: def async_entity_state_listener(
"""Set expose flag on newly created entities.""" changed_entity: str,
if event.data["action"] == "create": old_state: core.State | None,
async_should_expose(hass, DOMAIN, event.data["entity_id"]) new_state: core.State | None,
):
"""Set expose flag on new entities."""
if old_state is not None or new_state is None:
return
async_should_expose(hass, DOMAIN, changed_entity)
hass.bus.async_listen( @core.callback
er.EVENT_ENTITY_REGISTRY_UPDATED, def async_hass_started(hass: core.HomeAssistant) -> None:
async_handle_entity_registry_changed, """Set expose flag on all entities."""
run_immediately=True, for state in hass.states.async_all():
) async_should_expose(hass, DOMAIN, state.entity_id)
async_track_state_change(hass, MATCH_ALL, async_entity_state_listener)
start.async_at_started(hass, async_hass_started)
class DefaultAgent(AbstractConversationAgent): class DefaultAgent(AbstractConversationAgent):
@ -130,6 +140,11 @@ class DefaultAgent(AbstractConversationAgent):
self._async_handle_entity_registry_changed, self._async_handle_entity_registry_changed,
run_immediately=True, run_immediately=True,
) )
self.hass.bus.async_listen(
core.EVENT_STATE_CHANGED,
self._async_handle_state_changed,
run_immediately=True,
)
async_listen_entity_updates( async_listen_entity_updates(
self.hass, DOMAIN, self._async_exposed_entities_updated self.hass, DOMAIN, self._async_exposed_entities_updated
) )
@ -475,12 +490,19 @@ class DefaultAgent(AbstractConversationAgent):
@core.callback @core.callback
def _async_handle_entity_registry_changed(self, event: core.Event) -> None: def _async_handle_entity_registry_changed(self, event: core.Event) -> None:
"""Clear names list cache when an entity registry entry has changed.""" """Clear names list cache when an entity registry entry has changed."""
if event.data["action"] == "update" and not any( if event.data["action"] != "update" or not any(
field in event.data["changes"] for field in _ENTITY_REGISTRY_UPDATE_FIELDS field in event.data["changes"] for field in _ENTITY_REGISTRY_UPDATE_FIELDS
): ):
return return
self._slot_lists = None self._slot_lists = None
@core.callback
def _async_handle_state_changed(self, event: core.Event) -> None:
"""Clear names list cache when a state is added or removed from the state machine."""
if event.data.get("old_state") and event.data.get("new_state"):
return
self._slot_lists = None
@core.callback @core.callback
def _async_exposed_entities_updated(self) -> None: def _async_exposed_entities_updated(self) -> None:
"""Handle updated preferences.""" """Handle updated preferences."""
@ -493,30 +515,38 @@ class DefaultAgent(AbstractConversationAgent):
area_ids_with_entities: set[str] = set() area_ids_with_entities: set[str] = set()
entity_registry = er.async_get(self.hass) entity_registry = er.async_get(self.hass)
entities = [ states = [
entity state
for entity in entity_registry.entities.values() for state in self.hass.states.async_all()
if async_should_expose(self.hass, DOMAIN, entity.entity_id) if async_should_expose(self.hass, DOMAIN, state.entity_id)
] ]
devices = dr.async_get(self.hass) devices = dr.async_get(self.hass)
# Gather exposed entity names # Gather exposed entity names
entity_names = [] entity_names = []
for entity in entities: for state in states:
# Checked against "requires_context" and "excludes_context" in hassil # Checked against "requires_context" and "excludes_context" in hassil
context = {"domain": entity.domain} context = {"domain": state.domain}
if entity.device_class: if state.attributes:
context[ATTR_DEVICE_CLASS] = entity.device_class # Include some attributes
for attr in DEFAULT_EXPOSED_ATTRIBUTES:
if attr not in state.attributes:
continue
context[attr] = state.attributes[attr]
entity = entity_registry.async_get(state.entity_id)
if not entity:
# Default name
entity_names.append((state.name, state.name, context))
continue
if entity.aliases: if entity.aliases:
for alias in entity.aliases: for alias in entity.aliases:
entity_names.append((alias, alias, context)) entity_names.append((alias, alias, context))
# Default name # Default name
name = entity.async_friendly_name(self.hass) or entity.entity_id.replace( entity_names.append((state.name, state.name, context))
"_", " "
)
entity_names.append((name, name, context))
if entity.area_id: if entity.area_id:
# Expose area too # Expose area too

View file

@ -305,7 +305,11 @@ class ExposedEntities:
if domain in DEFAULT_EXPOSED_DOMAINS: if domain in DEFAULT_EXPOSED_DOMAINS:
return True return True
device_class = get_device_class(self._hass, entity_id) try:
device_class = get_device_class(self._hass, entity_id)
except HomeAssistantError:
# The entity no longer exists
return False
if ( if (
domain == "binary_sensor" domain == "binary_sensor"
and device_class in DEFAULT_EXPOSED_BINARY_SENSOR_DEVICE_CLASSES and device_class in DEFAULT_EXPOSED_BINARY_SENSOR_DEVICE_CLASSES

View file

@ -307,26 +307,6 @@ class RegistryEntry:
hass.states.async_set(self.entity_id, STATE_UNAVAILABLE, attrs) hass.states.async_set(self.entity_id, STATE_UNAVAILABLE, attrs)
def async_friendly_name(self, hass: HomeAssistant) -> str | None:
"""Return the friendly name.
If self.name is not None, this returns self.name
If has_entity_name is False, self.original_name
If has_entity_name is True, this returns device.name + self.original_name
"""
if not self.has_entity_name or self.name is not None:
return self.name or self.original_name
device_registry = dr.async_get(hass)
if not (device_id := self.device_id) or not (
device_entry := device_registry.async_get(device_id)
):
return self.original_name
if not (original_name := self.original_name):
return device_entry.name_by_user or device_entry.name
return f"{device_entry.name_by_user or device_entry.name} {original_name}"
class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]): class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]):
"""Store entity registry data.""" """Store entity registry data."""

View file

@ -1,4 +1,6 @@
"""The tests for the Air Quality component.""" """The tests for the Air Quality component."""
import pytest
from homeassistant.components.air_quality import ATTR_N2O, ATTR_OZONE, ATTR_PM_10 from homeassistant.components.air_quality import ATTR_N2O, ATTR_OZONE, ATTR_PM_10
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
@ -9,6 +11,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
async def test_state(hass: HomeAssistant) -> None: async def test_state(hass: HomeAssistant) -> None:
"""Test Air Quality state.""" """Test Air Quality state."""
config = {"air_quality": {"platform": "demo"}} config = {"air_quality": {"platform": "demo"}}

View file

@ -39,6 +39,7 @@ def events(hass: HomeAssistant) -> list[Event]:
@pytest.fixture @pytest.fixture
async def mock_camera(hass: HomeAssistant) -> None: async def mock_camera(hass: HomeAssistant) -> None:
"""Initialize a demo camera platform.""" """Initialize a demo camera platform."""
assert await async_setup_component(hass, "homeassistant", {})
assert await async_setup_component( assert await async_setup_component(
hass, "camera", {camera.DOMAIN: {"platform": "demo"}} hass, "camera", {camera.DOMAIN: {"platform": "demo"}}
) )

View file

@ -1539,6 +1539,7 @@ async def test_automation_restore_last_triggered_with_initial_state(
async def test_extraction_functions(hass: HomeAssistant) -> None: async def test_extraction_functions(hass: HomeAssistant) -> None:
"""Test extraction functions.""" """Test extraction functions."""
await async_setup_component(hass, "homeassistant", {})
await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}}) await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}})
assert await async_setup_component( assert await async_setup_component(
hass, hass,

View file

@ -0,0 +1,11 @@
"""Test fixtures for calendar sensor platforms."""
import pytest
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})

View file

@ -1,6 +1,8 @@
"""The tests for calendar recorder.""" """The tests for calendar recorder."""
from datetime import timedelta from datetime import timedelta
import pytest
from homeassistant.components.recorder import Recorder from homeassistant.components.recorder import Recorder
from homeassistant.components.recorder.history import get_significant_states from homeassistant.components.recorder.history import get_significant_states
from homeassistant.const import ATTR_FRIENDLY_NAME from homeassistant.const import ATTR_FRIENDLY_NAME
@ -12,9 +14,15 @@ from tests.common import async_fire_time_changed
from tests.components.recorder.common import async_wait_recording_done from tests.components.recorder.common import async_wait_recording_done
@pytest.fixture(autouse=True)
async def setup_homeassistant():
"""Override the fixture in calendar.conftest."""
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None: async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
"""Test sensor attributes to be excluded.""" """Test sensor attributes to be excluded."""
now = dt_util.utcnow() now = dt_util.utcnow()
await async_setup_component(hass, "homeassistant", {})
await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}}) await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}})
await hass.async_block_till_done() await hass.async_block_till_done()

View file

@ -5,11 +5,18 @@ import pytest
from homeassistant.components import camera from homeassistant.components import camera
from homeassistant.components.camera.const import StreamType from homeassistant.components.camera.const import StreamType
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .common import WEBRTC_ANSWER from .common import WEBRTC_ANSWER
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
@pytest.fixture(name="mock_camera") @pytest.fixture(name="mock_camera")
async def mock_camera_fixture(hass): async def mock_camera_fixture(hass):
"""Initialize a demo camera platform.""" """Initialize a demo camera platform."""

View file

@ -3,6 +3,8 @@ from __future__ import annotations
from datetime import timedelta from datetime import timedelta
import pytest
from homeassistant.components import camera from homeassistant.components import camera
from homeassistant.components.recorder import Recorder from homeassistant.components.recorder import Recorder
from homeassistant.components.recorder.history import get_significant_states from homeassistant.components.recorder.history import get_significant_states
@ -20,9 +22,15 @@ from tests.common import async_fire_time_changed
from tests.components.recorder.common import async_wait_recording_done from tests.components.recorder.common import async_wait_recording_done
@pytest.fixture(autouse=True)
async def setup_homeassistant():
"""Override the fixture in calendar.conftest."""
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None: async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
"""Test camera registered attributes to be excluded.""" """Test camera registered attributes to be excluded."""
now = dt_util.utcnow() now = dt_util.utcnow()
await async_setup_component(hass, "homeassistant", {})
await async_setup_component( await async_setup_component(
hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}}
) )

View file

@ -91,21 +91,21 @@ async def test_exposed_areas(
) )
device_registry.async_update_device(kitchen_device.id, area_id=area_kitchen.id) device_registry.async_update_device(kitchen_device.id, area_id=area_kitchen.id)
kitchen_light = entity_registry.async_get_or_create( kitchen_light = entity_registry.async_get_or_create("light", "demo", "1234")
"light", "demo", "1234", original_name="kitchen light"
)
entity_registry.async_update_entity( entity_registry.async_update_entity(
kitchen_light.entity_id, device_id=kitchen_device.id kitchen_light.entity_id, device_id=kitchen_device.id
) )
hass.states.async_set(kitchen_light.entity_id, "on") hass.states.async_set(
kitchen_light.entity_id, "on", attributes={ATTR_FRIENDLY_NAME: "kitchen light"}
bedroom_light = entity_registry.async_get_or_create(
"light", "demo", "5678", original_name="bedroom light"
) )
bedroom_light = entity_registry.async_get_or_create("light", "demo", "5678")
entity_registry.async_update_entity( entity_registry.async_update_entity(
bedroom_light.entity_id, area_id=area_bedroom.id bedroom_light.entity_id, area_id=area_bedroom.id
) )
hass.states.async_set(bedroom_light.entity_id, "on") hass.states.async_set(
bedroom_light.entity_id, "on", attributes={ATTR_FRIENDLY_NAME: "bedroom light"}
)
# Hide the bedroom light # Hide the bedroom light
expose_entity(hass, bedroom_light.entity_id, False) expose_entity(hass, bedroom_light.entity_id, False)
@ -156,6 +156,8 @@ async def test_expose_flag_automatically_set(
assert await async_setup_component(hass, "conversation", {}) assert await async_setup_component(hass, "conversation", {})
await hass.async_block_till_done() await hass.async_block_till_done()
with patch("homeassistant.components.http.start_http_server_and_save_config"):
await hass.async_start()
# After setting up conversation, the expose flag should now be set on all entities # After setting up conversation, the expose flag should now be set on all entities
assert async_get_assistant_settings(hass, conversation.DOMAIN) == { assert async_get_assistant_settings(hass, conversation.DOMAIN) == {
@ -164,10 +166,11 @@ async def test_expose_flag_automatically_set(
} }
# New entities will automatically have the expose flag set # New entities will automatically have the expose flag set
new_light = entity_registry.async_get_or_create("light", "demo", "2345") new_light = "light.demo_2345"
hass.states.async_set(new_light, "test")
await hass.async_block_till_done() await hass.async_block_till_done()
assert async_get_assistant_settings(hass, conversation.DOMAIN) == { assert async_get_assistant_settings(hass, conversation.DOMAIN) == {
light.entity_id: {"should_expose": True}, light.entity_id: {"should_expose": True},
new_light.entity_id: {"should_expose": True}, new_light: {"should_expose": True},
test.entity_id: {"should_expose": False}, test.entity_id: {"should_expose": False},
} }

View file

@ -202,11 +202,7 @@ async def test_http_processing_intent_entity_added_removed(
# Add an entity # Add an entity
entity_registry.async_get_or_create( entity_registry.async_get_or_create(
"light", "light", "demo", "5678", suggested_object_id="late"
"demo",
"5678",
suggested_object_id="late",
original_name="friendly light",
) )
hass.states.async_set("light.late", "off", {"friendly_name": "friendly light"}) hass.states.async_set("light.late", "off", {"friendly_name": "friendly light"})
@ -274,7 +270,7 @@ async def test_http_processing_intent_entity_added_removed(
} }
# Now delete the entity # Now delete the entity
entity_registry.async_remove("light.late") hass.states.async_remove("light.late")
client = await hass_client() client = await hass_client()
resp = await client.post( resp = await client.post(
@ -313,11 +309,7 @@ async def test_http_processing_intent_alias_added_removed(
so that the new alias is available. so that the new alias is available.
""" """
entity_registry.async_get_or_create( entity_registry.async_get_or_create(
"light", "light", "demo", "1234", suggested_object_id="kitchen"
"demo",
"1234",
suggested_object_id="kitchen",
original_name="kitchen light",
) )
hass.states.async_set("light.kitchen", "off", {"friendly_name": "kitchen light"}) hass.states.async_set("light.kitchen", "off", {"friendly_name": "kitchen light"})
@ -438,7 +430,6 @@ async def test_http_processing_intent_entity_renamed(
LIGHT_DOMAIN, LIGHT_DOMAIN,
{LIGHT_DOMAIN: [{"platform": "test"}]}, {LIGHT_DOMAIN: [{"platform": "test"}]},
) )
await hass.async_block_till_done()
calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on") calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on")
client = await hass_client() client = await hass_client()
@ -882,20 +873,9 @@ async def test_http_processing_intent_conversion_not_expose_new(
@pytest.mark.parametrize("agent_id", AGENT_ID_OPTIONS) @pytest.mark.parametrize("agent_id", AGENT_ID_OPTIONS)
@pytest.mark.parametrize("sentence", ("turn on kitchen", "turn kitchen on")) @pytest.mark.parametrize("sentence", ("turn on kitchen", "turn kitchen on"))
async def test_turn_on_intent( async def test_turn_on_intent(
hass: HomeAssistant, hass: HomeAssistant, init_components, sentence, agent_id
init_components,
entity_registry: er.EntityRegistry,
sentence,
agent_id,
) -> None: ) -> None:
"""Test calling the turn on intent.""" """Test calling the turn on intent."""
entity_registry.async_get_or_create(
"light",
"demo",
"1234",
suggested_object_id="kitchen",
original_name="kitchen",
)
hass.states.async_set("light.kitchen", "off") hass.states.async_set("light.kitchen", "off")
calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on") calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on")
@ -913,17 +893,8 @@ async def test_turn_on_intent(
@pytest.mark.parametrize("sentence", ("turn off kitchen", "turn kitchen off")) @pytest.mark.parametrize("sentence", ("turn off kitchen", "turn kitchen off"))
async def test_turn_off_intent( async def test_turn_off_intent(hass: HomeAssistant, init_components, sentence) -> None:
hass: HomeAssistant, init_components, entity_registry: er.EntityRegistry, sentence
) -> None:
"""Test calling the turn on intent.""" """Test calling the turn on intent."""
entity_registry.async_get_or_create(
"light",
"demo",
"1234",
suggested_object_id="kitchen",
original_name="kitchen",
)
hass.states.async_set("light.kitchen", "on") hass.states.async_set("light.kitchen", "on")
calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_off") calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_off")
@ -969,21 +940,11 @@ async def test_http_api_no_match(
async def test_http_api_handle_failure( async def test_http_api_handle_failure(
hass: HomeAssistant, hass: HomeAssistant, init_components, hass_client: ClientSessionGenerator
init_components,
entity_registry: er.EntityRegistry,
hass_client: ClientSessionGenerator,
) -> None: ) -> None:
"""Test the HTTP conversation API with an error during handling.""" """Test the HTTP conversation API with an error during handling."""
client = await hass_client() client = await hass_client()
entity_registry.async_get_or_create(
"light",
"demo",
"1234",
suggested_object_id="kitchen",
original_name="kitchen",
)
hass.states.async_set("light.kitchen", "off") hass.states.async_set("light.kitchen", "off")
# Raise an error during intent handling # Raise an error during intent handling
@ -1020,19 +981,11 @@ async def test_http_api_handle_failure(
async def test_http_api_unexpected_failure( async def test_http_api_unexpected_failure(
hass: HomeAssistant, hass: HomeAssistant,
init_components, init_components,
entity_registry: er.EntityRegistry,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
) -> None: ) -> None:
"""Test the HTTP conversation API with an unexpected error during handling.""" """Test the HTTP conversation API with an unexpected error during handling."""
client = await hass_client() client = await hass_client()
entity_registry.async_get_or_create(
"light",
"demo",
"1234",
suggested_object_id="kitchen",
original_name="kitchen",
)
hass.states.async_set("light.kitchen", "off") hass.states.async_set("light.kitchen", "off")
# Raise an "unexpected" error during intent handling # Raise an "unexpected" error during intent handling
@ -1355,17 +1308,8 @@ async def test_prepare_fail(hass: HomeAssistant) -> None:
assert not agent._lang_intents.get("not-a-language") assert not agent._lang_intents.get("not-a-language")
async def test_language_region( async def test_language_region(hass: HomeAssistant, init_components) -> None:
hass: HomeAssistant, init_components, entity_registry: er.EntityRegistry
) -> None:
"""Test calling the turn on intent.""" """Test calling the turn on intent."""
entity_registry.async_get_or_create(
"light",
"demo",
"1234",
suggested_object_id="kitchen",
original_name="kitchen",
)
hass.states.async_set("light.kitchen", "off") hass.states.async_set("light.kitchen", "off")
calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on") calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on")
@ -1414,17 +1358,8 @@ async def test_reload_on_new_component(hass: HomeAssistant) -> None:
assert {"light"} == (lang_intents.loaded_components - loaded_components) assert {"light"} == (lang_intents.loaded_components - loaded_components)
async def test_non_default_response( async def test_non_default_response(hass: HomeAssistant, init_components) -> None:
hass: HomeAssistant, init_components, entity_registry: er.EntityRegistry
) -> None:
"""Test intent response that is not the default.""" """Test intent response that is not the default."""
entity_registry.async_get_or_create(
"cover",
"demo",
"1234",
suggested_object_id="front_door",
original_name="front door",
)
hass.states.async_set("cover.front_door", "closed") hass.states.async_set("cover.front_door", "closed")
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER) calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)

View file

@ -218,6 +218,7 @@ async def test_discover_platform(
mock_demo_setup_scanner, mock_see, hass: HomeAssistant mock_demo_setup_scanner, mock_see, hass: HomeAssistant
) -> None: ) -> None:
"""Test discovery of device_tracker demo platform.""" """Test discovery of device_tracker demo platform."""
await async_setup_component(hass, "homeassistant", {})
with patch("homeassistant.components.device_tracker.legacy.update_config"): with patch("homeassistant.components.device_tracker.legacy.update_config"):
await discovery.async_load_platform( await discovery.async_load_platform(
hass, device_tracker.DOMAIN, "demo", {"test_key": "test_val"}, {"bla": {}} hass, device_tracker.DOMAIN, "demo", {"test_key": "test_val"}, {"bla": {}}

View file

@ -75,6 +75,12 @@ VALID_CONFIG = {
} }
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
@pytest.fixture @pytest.fixture
def mock_healthybox(): def mock_healthybox():
"""Mock fb.check_box_health.""" """Mock fb.check_box_health."""

View file

@ -209,6 +209,7 @@ async def test_send_text_command_expired_token_refresh_failure(
requires_reauth: ConfigEntryState, requires_reauth: ConfigEntryState,
) -> None: ) -> None:
"""Test failure refreshing token in send_text_command.""" """Test failure refreshing token in send_text_command."""
await async_setup_component(hass, "homeassistant", {})
await setup_integration() await setup_integration()
entries = hass.config_entries.async_entries(DOMAIN) entries = hass.config_entries.async_entries(DOMAIN)

View file

@ -43,6 +43,12 @@ MOCK_START_STREAM_SESSION_UUID = UUID("3303d503-17cc-469a-b672-92436a71a2f6")
PID_THAT_WILL_NEVER_BE_ALIVE = 2147483647 PID_THAT_WILL_NEVER_BE_ALIVE = 2147483647
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
async def _async_start_streaming(hass, acc): async def _async_start_streaming(hass, acc):
"""Start streaming a camera.""" """Start streaming a camera."""
acc.set_selected_stream_configuration(MOCK_START_STREAM_TLV) acc.set_selected_stream_configuration(MOCK_START_STREAM_TLV)

View file

@ -16,6 +16,12 @@ from tests.common import assert_setup_component, async_capture_events
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
@pytest.fixture @pytest.fixture
def aiohttp_unused_port(event_loop, aiohttp_unused_port, socket_enabled): def aiohttp_unused_port(event_loop, aiohttp_unused_port, socket_enabled):
"""Return aiohttp_unused_port and allow opening sockets.""" """Return aiohttp_unused_port and allow opening sockets."""

View file

@ -21,6 +21,12 @@ from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import ClientSessionGenerator, WebSocketGenerator from tests.typing import ClientSessionGenerator, WebSocketGenerator
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
async def test_get_image_http( async def test_get_image_http(
hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator
) -> None: ) -> None:

View file

@ -25,6 +25,7 @@ from tests.components.recorder.common import async_wait_recording_done
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None: async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
"""Test media_player registered attributes to be excluded.""" """Test media_player registered attributes to be excluded."""
now = dt_util.utcnow() now = dt_util.utcnow()
await async_setup_component(hass, "homeassistant", {})
await async_setup_component( await async_setup_component(
hass, media_player.DOMAIN, {media_player.DOMAIN: {"platform": "demo"}} hass, media_player.DOMAIN, {media_player.DOMAIN: {"platform": "demo"}}
) )

View file

@ -25,6 +25,12 @@ from tests.common import assert_setup_component, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
def create_group(hass, name): def create_group(hass, name):
"""Create a new person group. """Create a new person group.

View file

@ -26,6 +26,12 @@ CONFIG = {
ENDPOINT_URL = f"https://westus.{mf.FACE_API_URL}" ENDPOINT_URL = f"https://westus.{mf.FACE_API_URL}"
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
@pytest.fixture @pytest.fixture
def store_mock(): def store_mock():
"""Mock update store.""" """Mock update store."""

View file

@ -14,6 +14,12 @@ from tests.components.image_processing import common
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
@pytest.fixture @pytest.fixture
def store_mock(): def store_mock():
"""Mock update store.""" """Mock update store."""

View file

@ -14,6 +14,12 @@ from tests.components.image_processing import common
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
@pytest.fixture @pytest.fixture
async def setup_openalpr_cloud(hass): async def setup_openalpr_cloud(hass):
"""Set up openalpr cloud.""" """Set up openalpr cloud."""

View file

@ -14,6 +14,7 @@ from homeassistant.components.rtsp_to_webrtc import CONF_STUN_SERVER, DOMAIN
from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.components.websocket_api.const import TYPE_RESULT
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from .conftest import SERVER_URL, STREAM_SOURCE, ComponentSetup from .conftest import SERVER_URL, STREAM_SOURCE, ComponentSetup
@ -27,6 +28,12 @@ OFFER_SDP = "v=0\r\no=carol 28908764872 28908764872 IN IP4 100.3.6.6\r\n..."
ANSWER_SDP = "v=0\r\no=bob 2890844730 2890844730 IN IP4 host.example.com\r\n..." ANSWER_SDP = "v=0\r\no=bob 2890844730 2890844730 IN IP4 host.example.com\r\n..."
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
async def test_setup_success( async def test_setup_success(
hass: HomeAssistant, rtsp_to_webrtc_client: Any, setup_integration: ComponentSetup hass: HomeAssistant, rtsp_to_webrtc_client: Any, setup_integration: ComponentSetup
) -> None: ) -> None:

View file

@ -1373,6 +1373,7 @@ def test_compile_hourly_sum_statistics_negative_state(
mocksensor._attr_should_poll = False mocksensor._attr_should_poll = False
platform.ENTITIES["custom_sensor"] = mocksensor platform.ENTITIES["custom_sensor"] = mocksensor
setup_component(hass, "homeassistant", {})
setup_component( setup_component(
hass, "sensor", {"sensor": [{"platform": "demo"}, {"platform": "test"}]} hass, "sensor", {"sensor": [{"platform": "demo"}, {"platform": "test"}]}
) )

View file

@ -46,6 +46,12 @@ MOCK_DETECTIONS = {
MOCK_NOW = datetime.datetime(2020, 2, 20, 10, 5, 3) MOCK_NOW = datetime.datetime(2020, 2, 20, 10, 5, 3)
@pytest.fixture(autouse=True)
async def setup_homeassistant(hass: HomeAssistant):
"""Set up the homeassistant integration."""
await async_setup_component(hass, "homeassistant", {})
@pytest.fixture @pytest.fixture
def mock_detections(): def mock_detections():
"""Return a mock detection.""" """Return a mock detection."""

View file

@ -72,6 +72,8 @@ async def test_setup_legacy_service(hass: HomeAssistant) -> None:
}, },
} }
await async_setup_component(hass, "homeassistant", {})
with assert_setup_component(1, tts.DOMAIN): with assert_setup_component(1, tts.DOMAIN):
assert await async_setup_component(hass, tts.DOMAIN, config) assert await async_setup_component(hass, tts.DOMAIN, config)

View file

@ -1105,6 +1105,7 @@ async def test_state_template(hass: HomeAssistant) -> None:
async def test_browse_media(hass: HomeAssistant) -> None: async def test_browse_media(hass: HomeAssistant) -> None:
"""Test browse media.""" """Test browse media."""
await async_setup_component(hass, "homeassistant", {})
await async_setup_component( await async_setup_component(
hass, "media_player", {"media_player": {"platform": "demo"}} hass, "media_player", {"media_player": {"platform": "demo"}}
) )
@ -1135,6 +1136,7 @@ async def test_browse_media(hass: HomeAssistant) -> None:
async def test_browse_media_override(hass: HomeAssistant) -> None: async def test_browse_media_override(hass: HomeAssistant) -> None:
"""Test browse media override.""" """Test browse media override."""
await async_setup_component(hass, "homeassistant", {})
await async_setup_component( await async_setup_component(
hass, "media_player", {"media_player": {"platform": "demo"}} hass, "media_player", {"media_player": {"platform": "demo"}}
) )

View file

@ -18,6 +18,7 @@ from tests.components.recorder.common import async_wait_recording_done
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None: async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
"""Test weather attributes to be excluded.""" """Test weather attributes to be excluded."""
now = dt_util.utcnow() now = dt_util.utcnow()
await async_setup_component(hass, "homeassistant", {})
await async_setup_component(hass, DOMAIN, {DOMAIN: {"platform": "demo"}}) await async_setup_component(hass, DOMAIN, {DOMAIN: {"platform": "demo"}})
hass.config.units = METRIC_SYSTEM hass.config.units = METRIC_SYSTEM
await hass.async_block_till_done() await hass.async_block_till_done()