Fix deserialize bug + add test coverage (#92382)

This commit is contained in:
Paulus Schoutsen 2023-05-02 22:38:54 -04:00 committed by GitHub
parent 5ed41d3d51
commit 1baa4d5109
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 53 deletions

View File

@ -358,13 +358,15 @@ class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities
def _create_item(self, item_id: str, data: dict) -> ExposedEntity: def _create_item(self, item_id: str, data: dict) -> ExposedEntity:
"""Create an item from validated config.""" """Create an item from validated config."""
del data["entity_id"] return ExposedEntity(
return ExposedEntity(**data) assistants=data["assistants"],
)
def _deserialize_item(self, data: dict) -> ExposedEntity: def _deserialize_item(self, data: dict) -> ExposedEntity:
"""Create an item from its serialized representation.""" """Create an item from its serialized representation."""
del data["entity_id"] return ExposedEntity(
return ExposedEntity(**data) assistants=data["assistants"],
)
def _serialize_item(self, item_id: str, item: ExposedEntity) -> dict: def _serialize_item(self, item_id: str, item: ExposedEntity) -> dict:
"""Return the serialized representation of an item for storing.""" """Return the serialized representation of an item for storing."""

View File

@ -0,0 +1,25 @@
# serializer version: 1
# name: test_get_assistant_settings
dict({
'climate.test_unique1': mappingproxy({
'should_expose': True,
}),
'light.not_in_registry': dict({
'should_expose': True,
}),
})
# ---
# name: test_get_assistant_settings.1
dict({
})
# ---
# name: test_listeners
dict({
'light.kitchen': dict({
'should_expose': True,
}),
'switch.test_unique1': mappingproxy({
'should_expose': True,
}),
})
# ---

View File

@ -1,16 +1,19 @@
"""Test Home Assistant exposed entities helper.""" """Test Home Assistant exposed entities helper."""
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.homeassistant.exposed_entities import ( from homeassistant.components.homeassistant.exposed_entities import (
DATA_EXPOSED_ENTITIES, DATA_EXPOSED_ENTITIES,
ExposedEntities, ExposedEntities,
ExposedEntity, ExposedEntity,
async_expose_entity,
async_get_assistant_settings, async_get_assistant_settings,
async_listen_entity_updates, async_listen_entity_updates,
async_should_expose, async_should_expose,
) )
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, EntityCategory from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -30,11 +33,18 @@ async def test_load_preferences(hass: HomeAssistant) -> None:
assert list(exposed_entities._assistants) == ["test1", "test2"] assert list(exposed_entities._assistants) == ["test1", "test2"]
exposed_entities2 = ExposedEntities(hass) await exposed_entities.async_expose_entity("test1", "light.kitchen", True)
await exposed_entities.async_expose_entity("test1", "light.living_room", True)
await exposed_entities.async_expose_entity("test2", "light.kitchen", True)
await exposed_entities.async_expose_entity("test2", "light.kitchen", True)
await flush_store(exposed_entities.store) await flush_store(exposed_entities.store)
exposed_entities2 = ExposedEntities(hass)
await exposed_entities2.async_load() await exposed_entities2.async_load()
assert exposed_entities._assistants == exposed_entities2._assistants assert exposed_entities._assistants == exposed_entities2._assistants
assert exposed_entities.data == exposed_entities2.data
async def test_expose_entity( async def test_expose_entity(
@ -282,6 +292,7 @@ async def test_listen_updates(
async def test_get_assistant_settings( async def test_get_assistant_settings(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test get assistant settings.""" """Test get assistant settings."""
assert await async_setup_component(hass, "homeassistant", {}) assert await async_setup_component(hass, "homeassistant", {})
@ -294,16 +305,22 @@ async def test_get_assistant_settings(
assert async_get_assistant_settings(hass, "cloud.alexa") == {} assert async_get_assistant_settings(hass, "cloud.alexa") == {}
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True) await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
assert async_get_assistant_settings(hass, "cloud.alexa") == { await exposed_entities.async_expose_entity(
"climate.test_unique1": {"should_expose": True} "cloud.alexa", "light.not_in_registry", True
} )
assert async_get_assistant_settings(hass, "cloud.google_assistant") == {} assert async_get_assistant_settings(hass, "cloud.alexa") == snapshot
assert async_get_assistant_settings(hass, "cloud.google_assistant") == snapshot
with pytest.raises(HomeAssistantError):
exposed_entities.async_get_entity_settings("light.unknown")
@pytest.mark.parametrize("use_registry", [True, False])
async def test_should_expose( async def test_should_expose(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
use_registry: bool,
) -> None: ) -> None:
"""Test expose entity.""" """Test expose entity."""
ws_client = await hass_ws_client(hass) ws_client = await hass_ws_client(hass)
@ -325,68 +342,96 @@ async def test_should_expose(
assert await async_should_expose(hass, "test.test", "test.test") is False assert await async_should_expose(hass, "test.test", "test.test") is False
# Blocked entity is not exposed # Blocked entity is not exposed
entry_blocked = entity_registry.async_get_or_create( if use_registry:
"group", "test", "unique", suggested_object_id="all_locks" entry_blocked = entity_registry.async_get_or_create(
) "group", "test", "unique", suggested_object_id="all_locks"
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0] )
assert ( assert entry_blocked.entity_id == "group.all_locks"
await async_should_expose(hass, "cloud.alexa", entry_blocked.entity_id) is False assert CLOUD_NEVER_EXPOSED_ENTITIES[0] == "group.all_locks"
) assert await async_should_expose(hass, "cloud.alexa", "group.all_locks") is False
# Lock is exposed # Lock is exposed
lock1 = entity_registry.async_get_or_create("lock", "test", "unique1") if use_registry:
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0] entity_registry.async_get_or_create("lock", "test", "unique1")
assert await async_should_expose(hass, "cloud.alexa", lock1.entity_id) is True assert await async_should_expose(hass, "cloud.alexa", "lock.test_unique1") is True
# Hidden entity is not exposed # Hidden entity is not exposed
lock2 = entity_registry.async_get_or_create( if use_registry:
"lock", "test", "unique2", hidden_by=er.RegistryEntryHider.USER entity_registry.async_get_or_create(
) "lock", "test", "unique2", hidden_by=er.RegistryEntryHider.USER
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0] )
assert await async_should_expose(hass, "cloud.alexa", lock2.entity_id) is False assert (
await async_should_expose(hass, "cloud.alexa", "lock.test_unique2") is False
)
# Entity with category is not exposed # Entity with category is not exposed
lock3 = entity_registry.async_get_or_create( entity_registry.async_get_or_create(
"lock", "test", "unique3", entity_category=EntityCategory.CONFIG "lock", "test", "unique3", entity_category=EntityCategory.CONFIG
) )
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0] assert (
assert await async_should_expose(hass, "cloud.alexa", lock3.entity_id) is False await async_should_expose(hass, "cloud.alexa", "lock.test_unique3") is False
)
# Binary sensor without device class is not exposed # Binary sensor without device class is not exposed
binarysensor1 = entity_registry.async_get_or_create( if use_registry:
"binary_sensor", "test", "unique1" entity_registry.async_get_or_create("binary_sensor", "test", "unique1")
) else:
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0] hass.states.async_set("binary_sensor.test_unique1", "on", {})
assert ( assert (
await async_should_expose(hass, "cloud.alexa", binarysensor1.entity_id) is False await async_should_expose(hass, "cloud.alexa", "binary_sensor.test_unique1")
is False
) )
# Binary sensor with certain device class is exposed # Binary sensor with certain device class is exposed
binarysensor2 = entity_registry.async_get_or_create( if use_registry:
"binary_sensor", entity_registry.async_get_or_create(
"test", "binary_sensor",
"unique2", "test",
original_device_class="door", "unique2",
) original_device_class="door",
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0] )
else:
hass.states.async_set(
"binary_sensor.test_unique2", "on", {"device_class": "door"}
)
assert ( assert (
await async_should_expose(hass, "cloud.alexa", binarysensor2.entity_id) is True await async_should_expose(hass, "cloud.alexa", "binary_sensor.test_unique2")
is True
) )
# Sensor without device class is not exposed # Sensor without device class is not exposed
sensor1 = entity_registry.async_get_or_create("sensor", "test", "unique1") if use_registry:
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0] entity_registry.async_get_or_create("sensor", "test", "unique1")
assert await async_should_expose(hass, "cloud.alexa", sensor1.entity_id) is False else:
hass.states.async_set("sensor.test_unique1", "on", {})
assert (
await async_should_expose(hass, "cloud.alexa", "sensor.test_unique1") is False
)
# Sensor with certain device class is exposed # Sensor with certain device class is exposed
sensor2 = entity_registry.async_get_or_create( if use_registry:
"sensor", entity_registry.async_get_or_create(
"test", "sensor",
"unique2", "test",
original_device_class="temperature", "unique2",
original_device_class="temperature",
)
else:
hass.states.async_set(
"sensor.test_unique2", "on", {"device_class": "temperature"}
)
assert await async_should_expose(hass, "cloud.alexa", "sensor.test_unique2") is True
# The second time we check, it should load it from storage
assert await async_should_expose(hass, "cloud.alexa", "sensor.test_unique2") is True
# Check with a different assistant
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
exposed_entities.async_set_expose_new_entities("cloud.no_default_expose", False)
assert (
await async_should_expose(
hass, "cloud.no_default_expose", "sensor.test_unique2"
)
is False
) )
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
assert await async_should_expose(hass, "cloud.alexa", sensor2.entity_id) is True
async def test_list_exposed_entities( async def test_list_exposed_entities(
@ -441,3 +486,21 @@ async def test_list_exposed_entities(
"test.test_unique2": {"cloud.alexa": True, "cloud.google_assistant": True}, "test.test_unique2": {"cloud.alexa": True, "cloud.google_assistant": True},
}, },
} }
async def test_listeners(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Make sure we call entity listeners."""
assert await async_setup_component(hass, "homeassistant", {})
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
callbacks = []
exposed_entities.async_listen_entity_updates("test1", lambda: callbacks.append(1))
await async_expose_entity(hass, "test1", "light.kitchen", True)
assert len(callbacks) == 1
entry1 = entity_registry.async_get_or_create("switch", "test", "unique1")
await async_expose_entity(hass, "test1", entry1.entity_id, True)