Flush conversation name cache when an entity is renamed (#91214)

This commit is contained in:
Erik Montnemery 2023-04-11 14:33:08 +02:00 committed by GitHub
parent 0d7711f787
commit 3c8397a7b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 289 additions and 4 deletions

View file

@ -32,6 +32,7 @@ from .const import DEFAULT_EXPOSED_ATTRIBUTES, DEFAULT_EXPOSED_DOMAINS, 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"
_ENTITY_REGISTRY_UPDATE_FIELDS = ["aliases", "name", "original_name"]
REGEX_TYPE = type(re.compile("")) REGEX_TYPE = type(re.compile(""))
@ -450,8 +451,10 @@ 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 changes aliases.""" """Clear names list cache when an entity registry entry has changed."""
if event.data["action"] == "update" and "aliases" not in event.data["changes"]: if event.data["action"] == "update" and not any(
field in event.data["changes"] for field in _ENTITY_REGISTRY_UPDATE_FIELDS
):
return return
self._slot_lists = None self._slot_lists = None

View file

@ -148,7 +148,7 @@ async def test_http_processing_intent_target_ha_agent(
} }
async def test_http_processing_intent_entity_added( async def test_http_processing_intent_entity_added_removed(
hass: HomeAssistant, hass: HomeAssistant,
init_components, init_components,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
@ -198,7 +198,7 @@ async def test_http_processing_intent_entity_added(
"conversation_id": None, "conversation_id": None,
} }
# Add an alias # Add an entity
entity_registry.async_get_or_create( entity_registry.async_get_or_create(
"light", "demo", "5678", suggested_object_id="late" "light", "demo", "5678", suggested_object_id="late"
) )
@ -294,6 +294,288 @@ async def test_http_processing_intent_entity_added(
} }
async def test_http_processing_intent_alias_added_removed(
hass: HomeAssistant,
init_components,
hass_client: ClientSessionGenerator,
hass_admin_user: MockUser,
entity_registry: er.EntityRegistry,
) -> None:
"""Test processing intent via HTTP API with aliases added later.
We want to ensure that adding an alias later busts the cache
so that the new alias is available.
"""
entity_registry.async_get_or_create(
"light", "demo", "1234", suggested_object_id="kitchen"
)
hass.states.async_set("light.kitchen", "off", {"friendly_name": "kitchen light"})
calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on")
client = await hass_client()
resp = await client.post(
"/api/conversation/process", json={"text": "turn on kitchen light"}
)
assert resp.status == HTTPStatus.OK
assert len(calls) == 1
data = await resp.json()
assert data == {
"response": {
"response_type": "action_done",
"card": {},
"speech": {
"plain": {
"extra_data": None,
"speech": "Turned on light",
}
},
"language": hass.config.language,
"data": {
"targets": [],
"success": [
{"id": "light.kitchen", "name": "kitchen light", "type": "entity"}
],
"failed": [],
},
},
"conversation_id": None,
}
# Add an alias
entity_registry.async_update_entity("light.kitchen", aliases={"late added alias"})
client = await hass_client()
resp = await client.post(
"/api/conversation/process", json={"text": "turn on late added alias"}
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert data == {
"response": {
"response_type": "action_done",
"card": {},
"speech": {
"plain": {
"extra_data": None,
"speech": "Turned on light",
}
},
"language": hass.config.language,
"data": {
"targets": [],
"success": [
{"id": "light.kitchen", "name": "kitchen light", "type": "entity"}
],
"failed": [],
},
},
"conversation_id": None,
}
# Now remove the alieas
entity_registry.async_update_entity("light.kitchen", aliases={})
client = await hass_client()
resp = await client.post(
"/api/conversation/process", json={"text": "turn on late added alias"}
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert data == {
"conversation_id": None,
"response": {
"card": {},
"data": {"code": "no_intent_match"},
"language": hass.config.language,
"response_type": "error",
"speech": {
"plain": {
"extra_data": None,
"speech": "Sorry, I couldn't understand that",
}
},
},
}
async def test_http_processing_intent_entity_renamed(
hass: HomeAssistant,
init_components,
hass_client: ClientSessionGenerator,
hass_admin_user: MockUser,
entity_registry: er.EntityRegistry,
enable_custom_integrations: None,
) -> None:
"""Test processing intent via HTTP API with entities renamed later.
We want to ensure that renaming an entity later busts the cache
so that the new name is used.
"""
platform = getattr(hass.components, "test.light")
platform.init(empty=True)
entity = platform.MockLight("kitchen light", "on")
entity._attr_unique_id = "1234"
entity.entity_id = "light.kitchen"
platform.ENTITIES.append(entity)
assert await async_setup_component(
hass,
LIGHT_DOMAIN,
{LIGHT_DOMAIN: [{"platform": "test"}]},
)
calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on")
client = await hass_client()
resp = await client.post(
"/api/conversation/process", json={"text": "turn on kitchen light"}
)
assert resp.status == HTTPStatus.OK
assert len(calls) == 1
data = await resp.json()
assert data == {
"response": {
"response_type": "action_done",
"card": {},
"speech": {
"plain": {
"extra_data": None,
"speech": "Turned on light",
}
},
"language": hass.config.language,
"data": {
"targets": [],
"success": [
{"id": "light.kitchen", "name": "kitchen light", "type": "entity"}
],
"failed": [],
},
},
"conversation_id": None,
}
# Rename the entity
entity_registry.async_update_entity("light.kitchen", name="renamed light")
await hass.async_block_till_done()
client = await hass_client()
resp = await client.post(
"/api/conversation/process", json={"text": "turn on renamed light"}
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert data == {
"response": {
"response_type": "action_done",
"card": {},
"speech": {
"plain": {
"extra_data": None,
"speech": "Turned on light",
}
},
"language": hass.config.language,
"data": {
"targets": [],
"success": [
{"id": "light.kitchen", "name": "renamed light", "type": "entity"}
],
"failed": [],
},
},
"conversation_id": None,
}
client = await hass_client()
resp = await client.post(
"/api/conversation/process", json={"text": "turn on kitchen light"}
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert data == {
"conversation_id": None,
"response": {
"card": {},
"data": {"code": "no_intent_match"},
"language": hass.config.language,
"response_type": "error",
"speech": {
"plain": {
"extra_data": None,
"speech": "Sorry, I couldn't understand that",
}
},
},
}
# Now clear the custom name
entity_registry.async_update_entity("light.kitchen", name=None)
await hass.async_block_till_done()
client = await hass_client()
resp = await client.post(
"/api/conversation/process", json={"text": "turn on kitchen light"}
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert data == {
"response": {
"response_type": "action_done",
"card": {},
"speech": {
"plain": {
"extra_data": None,
"speech": "Turned on light",
}
},
"language": hass.config.language,
"data": {
"targets": [],
"success": [
{"id": "light.kitchen", "name": "kitchen light", "type": "entity"}
],
"failed": [],
},
},
"conversation_id": None,
}
client = await hass_client()
resp = await client.post(
"/api/conversation/process", json={"text": "turn on renamed light"}
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert data == {
"conversation_id": None,
"response": {
"card": {},
"data": {"code": "no_intent_match"},
"language": hass.config.language,
"response_type": "error",
"speech": {
"plain": {
"extra_data": None,
"speech": "Sorry, I couldn't understand that",
}
},
},
}
@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(