From dc8e87a6f70439f9830d93d03c53d6ff098a4861 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 23 Mar 2022 12:40:28 +0100 Subject: [PATCH] Exclude hidden entities from alexa (#68555) --- .../components/alexa/smart_home_http.py | 5 +- tests/components/alexa/__init__.py | 39 +++++------ tests/components/alexa/test_capabilities.py | 10 +-- tests/components/alexa/test_entities.py | 66 ++++++++++++++++++- tests/components/alexa/test_smart_home.py | 36 +++++----- tests/components/alexa/test_state_report.py | 26 ++++---- 6 files changed, 123 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/alexa/smart_home_http.py b/homeassistant/components/alexa/smart_home_http.py index 95e13adfbd9..6a953a9f9d4 100644 --- a/homeassistant/components/alexa/smart_home_http.py +++ b/homeassistant/components/alexa/smart_home_http.py @@ -68,7 +68,10 @@ class AlexaConfig(AbstractConfig): entity_registry = er.async_get(self.hass) if registry_entry := entity_registry.async_get(entity_id): - auxiliary_entity = registry_entry.entity_category is not None + auxiliary_entity = ( + registry_entry.entity_category is not None + or registry_entry.hidden_by is not None + ) else: auxiliary_entity = False return not auxiliary_entity diff --git a/tests/components/alexa/__init__.py b/tests/components/alexa/__init__.py index 053100d2e00..1f0854c5102 100644 --- a/tests/components/alexa/__init__.py +++ b/tests/components/alexa/__init__.py @@ -3,8 +3,10 @@ import re from unittest.mock import Mock from uuid import uuid4 -from homeassistant.components.alexa import config, smart_home +from homeassistant.components.alexa import config, smart_home, smart_home_http +from homeassistant.components.alexa.const import CONF_ENDPOINT, CONF_FILTER, CONF_LOCALE from homeassistant.core import Context, callback +from homeassistant.helpers import entityfilter from tests.common import async_mock_service @@ -13,7 +15,7 @@ TEST_TOKEN_URL = "https://api.amazon.com/auth/o2/token" TEST_LOCALE = "en-US" -class MockConfig(config.AbstractConfig): +class MockConfig(smart_home_http.AlexaConfig): """Mock Alexa config.""" entity_config = { @@ -26,7 +28,14 @@ class MockConfig(config.AbstractConfig): def __init__(self, hass): """Mock Alexa config.""" - super().__init__(hass) + super().__init__( + hass, + { + CONF_ENDPOINT: TEST_URL, + CONF_FILTER: entityfilter.FILTER_SCHEMA({}), + CONF_LOCALE: TEST_LOCALE, + }, + ) self._store = Mock(spec_set=config.AlexaConfigStore) @property @@ -34,25 +43,11 @@ class MockConfig(config.AbstractConfig): """Return if config supports auth.""" return True - @property - def endpoint(self): - """Endpoint for report state.""" - return TEST_URL - - @property - def locale(self): - """Return config locale.""" - return TEST_LOCALE - @callback def user_identifier(self): """Return an identifier for the user that represents this config.""" return "mock-user-id" - def should_expose(self, entity_id): - """If an entity should be exposed.""" - return True - @callback def async_invalidate_access_token(self): """Invalidate access token.""" @@ -65,9 +60,9 @@ class MockConfig(config.AbstractConfig): """Accept a grant.""" -def get_default_config(): +def get_default_config(hass): """Return a MockConfig instance.""" - return MockConfig(None) + return MockConfig(hass) def get_new_request(namespace, name, endpoint=None): @@ -117,7 +112,7 @@ async def assert_request_calls_service( calls = async_mock_service(hass, domain, service_name) msg = await smart_home.async_handle_message( - hass, get_default_config(), request, context + hass, get_default_config(hass), request, context ) await hass.async_block_till_done() @@ -142,7 +137,7 @@ async def assert_request_fails( domain, service_name = service_not_called.split(".") call = async_mock_service(hass, domain, service_name) - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() assert not call @@ -201,7 +196,7 @@ async def reported_properties(hass, endpoint, return_full_response=False): assertions about the properties. """ request = get_new_request("Alexa", "ReportState", endpoint) - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() if return_full_response: return msg diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index d24849e1006..3f76c6bee04 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -55,7 +55,7 @@ async def test_api_adjust_brightness(hass, adjust): call_light = async_mock_service(hass, "light", "turn_on") - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() assert "event" in msg @@ -85,7 +85,7 @@ async def test_api_set_color_rgb(hass): call_light = async_mock_service(hass, "light", "turn_on") - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() assert "event" in msg @@ -111,7 +111,7 @@ async def test_api_set_color_temperature(hass): call_light = async_mock_service(hass, "light", "turn_on") - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() assert "event" in msg @@ -139,7 +139,7 @@ async def test_api_decrease_color_temp(hass, result, initial): call_light = async_mock_service(hass, "light", "turn_on") - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() assert "event" in msg @@ -167,7 +167,7 @@ async def test_api_increase_color_temp(hass, result, initial): call_light = async_mock_service(hass, "light", "turn_on") - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() assert "event" in msg diff --git a/tests/components/alexa/test_entities.py b/tests/components/alexa/test_entities.py index 54e48df8e8e..7b2a455be92 100644 --- a/tests/components/alexa/test_entities.py +++ b/tests/components/alexa/test_entities.py @@ -3,6 +3,8 @@ from unittest.mock import patch from homeassistant.components.alexa import smart_home from homeassistant.const import __version__ +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.entity import EntityCategory from . import get_default_config, get_new_request @@ -13,7 +15,63 @@ async def test_unsupported_domain(hass): hass.states.async_set("woz.boop", "on", {"friendly_name": "Boop Woz"}) - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) + + assert "event" in msg + msg = msg["event"] + + assert not msg["payload"]["endpoints"] + + +async def test_categorized_hidden_entities(hass): + """Discovery ignores hidden and categorized entities.""" + entity_registry = er.async_get(hass) + request = get_new_request("Alexa.Discovery", "Discover") + + entity_entry1 = entity_registry.async_get_or_create( + "switch", + "test", + "switch_config_id", + suggested_object_id="config_switch", + entity_category=EntityCategory.CONFIG, + ) + entity_entry2 = entity_registry.async_get_or_create( + "switch", + "test", + "switch_diagnostic_id", + suggested_object_id="diagnostic_switch", + entity_category=EntityCategory.DIAGNOSTIC, + ) + entity_entry3 = entity_registry.async_get_or_create( + "switch", + "test", + "switch_system_id", + suggested_object_id="system_switch", + entity_category=EntityCategory.SYSTEM, + ) + entity_entry4 = entity_registry.async_get_or_create( + "switch", + "test", + "switch_hidden_integration_id", + suggested_object_id="hidden_integration_switch", + hidden_by=er.RegistryEntryHider.INTEGRATION, + ) + entity_entry5 = entity_registry.async_get_or_create( + "switch", + "test", + "switch_hidden_user_id", + suggested_object_id="hidden_user_switch", + hidden_by=er.RegistryEntryHider.USER, + ) + + # These should not show up in the sync request + hass.states.async_set(entity_entry1.entity_id, "on") + hass.states.async_set(entity_entry2.entity_id, "something_else") + hass.states.async_set(entity_entry3.entity_id, "blah") + hass.states.async_set(entity_entry4.entity_id, "foo") + hass.states.async_set(entity_entry5.entity_id, "bar") + + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) assert "event" in msg msg = msg["event"] @@ -27,7 +85,7 @@ async def test_serialize_discovery(hass): hass.states.async_set("switch.bla", "on", {"friendly_name": "Boop Woz"}) - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) assert "event" in msg msg = msg["event"] @@ -51,7 +109,9 @@ async def test_serialize_discovery_recovers(hass, caplog): "homeassistant.components.alexa.capabilities.AlexaPowerController.serialize_discovery", side_effect=TypeError, ): - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message( + hass, get_default_config(hass), request + ) assert "event" in msg msg = msg["event"] diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 7ebba26113d..9fb6584fae3 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -121,7 +121,7 @@ async def test_wrong_version(hass): msg["directive"]["header"]["payloadVersion"] = "2" with pytest.raises(AssertionError): - await smart_home.async_handle_message(hass, get_default_config(), msg) + await smart_home.async_handle_message(hass, get_default_config(hass), msg) async def discovery_test(device, hass, expected_endpoints=1): @@ -131,7 +131,7 @@ async def discovery_test(device, hass, expected_endpoints=1): # setup test devices hass.states.async_set(*device) - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) assert "event" in msg msg = msg["event"] @@ -2308,7 +2308,7 @@ async def test_api_entity_not_exists(hass): call_switch = async_mock_service(hass, "switch", "turn_on") - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() assert "event" in msg @@ -2323,7 +2323,7 @@ async def test_api_entity_not_exists(hass): async def test_api_function_not_implemented(hass): """Test api call that is not implemented to us.""" request = get_new_request("Alexa.HAHAAH", "Sweet") - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) assert "event" in msg msg = msg["event"] @@ -2347,7 +2347,7 @@ async def test_api_accept_grant(hass): } # setup test devices - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message(hass, get_default_config(hass), request) await hass.async_block_till_done() assert "event" in msg @@ -2400,7 +2400,9 @@ async def test_logging_request(hass, events): """Test that we log requests.""" context = Context() request = get_new_request("Alexa.Discovery", "Discover") - await smart_home.async_handle_message(hass, get_default_config(), request, context) + await smart_home.async_handle_message( + hass, get_default_config(hass), request, context + ) # To trigger event listener await hass.async_block_till_done() @@ -2420,7 +2422,9 @@ async def test_logging_request_with_entity(hass, events): """Test that we log requests.""" context = Context() request = get_new_request("Alexa.PowerController", "TurnOn", "switch#xy") - await smart_home.async_handle_message(hass, get_default_config(), request, context) + await smart_home.async_handle_message( + hass, get_default_config(hass), request, context + ) # To trigger event listener await hass.async_block_till_done() @@ -2446,7 +2450,7 @@ async def test_disabled(hass): call_switch = async_mock_service(hass, "switch", "turn_on") msg = await smart_home.async_handle_message( - hass, get_default_config(), request, enabled=False + hass, get_default_config(hass), request, enabled=False ) await hass.async_block_till_done() @@ -2630,7 +2634,7 @@ async def test_range_unsupported_domain(hass): request["directive"]["header"]["instance"] = "switch.speed" msg = await smart_home.async_handle_message( - hass, get_default_config(), request, context + hass, get_default_config(hass), request, context ) assert "event" in msg @@ -2651,7 +2655,7 @@ async def test_mode_unsupported_domain(hass): request["directive"]["header"]["instance"] = "switch.direction" msg = await smart_home.async_handle_message( - hass, get_default_config(), request, context + hass, get_default_config(hass), request, context ) assert "event" in msg @@ -3393,7 +3397,7 @@ async def test_media_player_eq_bands_not_supported(hass): ) request["directive"]["payload"] = {"bands": [{"name": "BASS", "value": -2}]} msg = await smart_home.async_handle_message( - hass, get_default_config(), request, context + hass, get_default_config(hass), request, context ) assert "event" in msg @@ -3410,7 +3414,7 @@ async def test_media_player_eq_bands_not_supported(hass): "bands": [{"name": "BASS", "levelDelta": 3, "levelDirection": "UP"}] } msg = await smart_home.async_handle_message( - hass, get_default_config(), request, context + hass, get_default_config(hass), request, context ) assert "event" in msg @@ -3427,7 +3431,7 @@ async def test_media_player_eq_bands_not_supported(hass): "bands": [{"name": "BASS", "levelDelta": 3, "levelDirection": "UP"}] } msg = await smart_home.async_handle_message( - hass, get_default_config(), request, context + hass, get_default_config(hass), request, context ) assert "event" in msg @@ -3928,7 +3932,9 @@ async def test_initialize_camera_stream(hass, mock_camera, mock_stream): "homeassistant.components.demo.camera.DemoCamera.stream_source", return_value="rtsp://example.local", ): - msg = await smart_home.async_handle_message(hass, get_default_config(), request) + msg = await smart_home.async_handle_message( + hass, get_default_config(hass), request + ) await hass.async_block_till_done() assert "event" in msg @@ -3982,7 +3988,7 @@ async def test_api_message_sets_authorized(hass): msg = get_new_request("Alexa.PowerController", "TurnOn", "switch#xy") async_mock_service(hass, "switch", "turn_on") - config = get_default_config() + config = get_default_config(hass) config._store.set_authorized.assert_not_called() await smart_home.async_handle_message(hass, config, msg) config._store.set_authorized.assert_called_once_with(True) diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index 06c7d051798..bd47a80c18c 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -21,7 +21,7 @@ async def test_report_state(hass, aioclient_mock): {"friendly_name": "Test Contact Sensor", "device_class": "door"}, ) - await state_report.async_enable_proactive_mode(hass, get_default_config()) + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) hass.states.async_set( "binary_sensor.test_contact", @@ -66,7 +66,7 @@ async def test_report_state_fail(hass, aioclient_mock, caplog): {"friendly_name": "Test Contact Sensor", "device_class": "door"}, ) - await state_report.async_enable_proactive_mode(hass, get_default_config()) + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) hass.states.async_set( "binary_sensor.test_contact", @@ -100,7 +100,7 @@ async def test_report_state_timeout(hass, aioclient_mock, caplog): {"friendly_name": "Test Contact Sensor", "device_class": "door"}, ) - await state_report.async_enable_proactive_mode(hass, get_default_config()) + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) hass.states.async_set( "binary_sensor.test_contact", @@ -134,7 +134,7 @@ async def test_report_state_retry(hass, aioclient_mock): {"friendly_name": "Test Contact Sensor", "device_class": "door"}, ) - await state_report.async_enable_proactive_mode(hass, get_default_config()) + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) hass.states.async_set( "binary_sensor.test_contact", @@ -162,7 +162,7 @@ async def test_report_state_unsets_authorized_on_error(hass, aioclient_mock): {"friendly_name": "Test Contact Sensor", "device_class": "door"}, ) - config = get_default_config() + config = get_default_config(hass) await state_report.async_enable_proactive_mode(hass, config) hass.states.async_set( @@ -191,7 +191,7 @@ async def test_report_state_unsets_authorized_on_access_token_error( {"friendly_name": "Test Contact Sensor", "device_class": "door"}, ) - config = get_default_config() + config = get_default_config(hass) await state_report.async_enable_proactive_mode(hass, config) @@ -226,7 +226,7 @@ async def test_report_state_instance(hass, aioclient_mock): }, ) - await state_report.async_enable_proactive_mode(hass, get_default_config()) + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) hass.states.async_set( "fan.test_fan", @@ -296,7 +296,7 @@ async def test_send_add_or_update_message(hass, aioclient_mock): "zwave.bla", # Unsupported ] await state_report.async_send_add_or_update_message( - hass, get_default_config(), entities + hass, get_default_config(hass), entities ) assert len(aioclient_mock.mock_calls) == 1 @@ -323,7 +323,7 @@ async def test_send_delete_message(hass, aioclient_mock): ) await state_report.async_send_delete_message( - hass, get_default_config(), ["binary_sensor.test_contact", "zwave.bla"] + hass, get_default_config(hass), ["binary_sensor.test_contact", "zwave.bla"] ) assert len(aioclient_mock.mock_calls) == 1 @@ -349,7 +349,7 @@ async def test_doorbell_event(hass, aioclient_mock): {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, ) - await state_report.async_enable_proactive_mode(hass, get_default_config()) + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) hass.states.async_set( "binary_sensor.test_doorbell", @@ -407,7 +407,7 @@ async def test_doorbell_event_fail(hass, aioclient_mock, caplog): {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, ) - await state_report.async_enable_proactive_mode(hass, get_default_config()) + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) hass.states.async_set( "binary_sensor.test_doorbell", @@ -441,7 +441,7 @@ async def test_doorbell_event_timeout(hass, aioclient_mock, caplog): {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, ) - await state_report.async_enable_proactive_mode(hass, get_default_config()) + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) hass.states.async_set( "binary_sensor.test_doorbell", @@ -464,7 +464,7 @@ async def test_doorbell_event_timeout(hass, aioclient_mock, caplog): async def test_proactive_mode_filter_states(hass, aioclient_mock): """Test all the cases that filter states.""" aioclient_mock.post(TEST_URL, text="", status=202) - config = get_default_config() + config = get_default_config(hass) await state_report.async_enable_proactive_mode(hass, config) # First state should report