core/tests/components/conversation/test_init.py

231 lines
7.5 KiB
Python

"""The tests for the Conversation component."""
from http import HTTPStatus
from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
import voluptuous as vol
from homeassistant.components import conversation
from homeassistant.components.conversation import default_agent
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent
from homeassistant.setup import async_setup_component
from . import MockAgent
from tests.common import MockUser, async_mock_service
from tests.typing import ClientSessionGenerator
AGENT_ID_OPTIONS = [
None,
# Old value of conversation.HOME_ASSISTANT_AGENT,
"homeassistant",
# Current value of conversation.HOME_ASSISTANT_AGENT,
"conversation.home_assistant",
]
@pytest.mark.parametrize("agent_id", AGENT_ID_OPTIONS)
@pytest.mark.parametrize("sentence", ["turn on kitchen", "turn kitchen on"])
@pytest.mark.parametrize("conversation_id", ["my_new_conversation", None])
async def test_turn_on_intent(
hass: HomeAssistant,
init_components,
conversation_id,
sentence,
agent_id,
snapshot: SnapshotAssertion,
) -> None:
"""Test calling the turn on intent."""
hass.states.async_set("light.kitchen", "off")
calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on")
data = {conversation.ATTR_TEXT: sentence}
if agent_id is not None:
data[conversation.ATTR_AGENT_ID] = agent_id
if conversation_id is not None:
data[conversation.ATTR_CONVERSATION_ID] = conversation_id
result = await hass.services.async_call(
"conversation",
"process",
data,
blocking=True,
return_response=True,
)
assert len(calls) == 1
call = calls[0]
assert call.domain == LIGHT_DOMAIN
assert call.service == "turn_on"
assert call.data == {"entity_id": ["light.kitchen"]}
assert result == snapshot
async def test_service_fails(hass: HomeAssistant, init_components) -> None:
"""Test calling the turn on intent."""
with (
pytest.raises(HomeAssistantError),
patch(
"homeassistant.components.conversation.async_converse",
side_effect=intent.IntentHandleError,
),
):
await hass.services.async_call(
"conversation",
"process",
{"text": "bla"},
blocking=True,
)
@pytest.mark.parametrize("sentence", ["turn off kitchen", "turn kitchen off"])
async def test_turn_off_intent(hass: HomeAssistant, init_components, sentence) -> None:
"""Test calling the turn on intent."""
hass.states.async_set("light.kitchen", "on")
calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_off")
await hass.services.async_call(
"conversation", "process", {conversation.ATTR_TEXT: sentence}
)
await hass.async_block_till_done()
assert len(calls) == 1
call = calls[0]
assert call.domain == LIGHT_DOMAIN
assert call.service == "turn_off"
assert call.data == {"entity_id": ["light.kitchen"]}
@pytest.mark.usefixtures("init_components")
async def test_custom_agent(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
hass_admin_user: MockUser,
mock_conversation_agent: MockAgent,
snapshot: SnapshotAssertion,
) -> None:
"""Test a custom conversation agent."""
client = await hass_client()
data = {
"text": "Test Text",
"conversation_id": "test-conv-id",
"language": "test-language",
"agent_id": mock_conversation_agent.agent_id,
}
resp = await client.post("/api/conversation/process", json=data)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert data == snapshot
assert data["response"]["response_type"] == "action_done"
assert data["response"]["speech"]["plain"]["speech"] == "Test response"
assert data["conversation_id"] == "test-conv-id"
assert len(mock_conversation_agent.calls) == 1
assert mock_conversation_agent.calls[0].text == "Test Text"
assert mock_conversation_agent.calls[0].context.user_id == hass_admin_user.id
assert mock_conversation_agent.calls[0].conversation_id == "test-conv-id"
assert mock_conversation_agent.calls[0].language == "test-language"
conversation.async_unset_agent(
hass, hass.config_entries.async_get_entry(mock_conversation_agent.agent_id)
)
async def test_prepare_reload(hass: HomeAssistant, init_components) -> None:
"""Test calling the reload service."""
language = hass.config.language
# Load intents
agent = default_agent.async_get_default_agent(hass)
assert isinstance(agent, default_agent.DefaultAgent)
await agent.async_prepare(language)
# Confirm intents are loaded
assert agent._lang_intents.get(language)
# Try to clear for a different language
await hass.services.async_call("conversation", "reload", {"language": "elvish"})
await hass.async_block_till_done()
# Confirm intents are still loaded
assert agent._lang_intents.get(language)
# Clear cache for all languages
await hass.services.async_call("conversation", "reload", {})
await hass.async_block_till_done()
# Confirm intent cache is cleared
assert not agent._lang_intents.get(language)
async def test_prepare_fail(hass: HomeAssistant) -> None:
"""Test calling prepare with a non-existent language."""
assert await async_setup_component(hass, "homeassistant", {})
assert await async_setup_component(hass, "conversation", {})
# Load intents
agent = default_agent.async_get_default_agent(hass)
assert isinstance(agent, default_agent.DefaultAgent)
await agent.async_prepare("not-a-language")
# Confirm no intents were loaded
assert agent._lang_intents.get("not-a-language") is default_agent.ERROR_SENTINEL
async def test_agent_id_validator_invalid_agent(
hass: HomeAssistant, init_components
) -> None:
"""Test validating agent id."""
with pytest.raises(vol.Invalid):
conversation.agent_id_validator("invalid_agent")
conversation.agent_id_validator(conversation.HOME_ASSISTANT_AGENT)
conversation.agent_id_validator("conversation.home_assistant")
async def test_get_agent_info(
hass: HomeAssistant,
init_components,
mock_conversation_agent: MockAgent,
snapshot: SnapshotAssertion,
) -> None:
"""Test get agent info."""
agent_info = conversation.async_get_agent_info(hass)
# Test it's the default
assert conversation.async_get_agent_info(hass, "homeassistant") == agent_info
assert conversation.async_get_agent_info(hass, "homeassistant") == snapshot
assert (
conversation.async_get_agent_info(hass, mock_conversation_agent.agent_id)
== snapshot
)
assert conversation.async_get_agent_info(hass, "not exist") is None
# Test the name when config entry title is empty
agent_entry = hass.config_entries.async_get_entry("mock-entry")
hass.config_entries.async_update_entry(agent_entry, title="")
agent_info = conversation.async_get_agent_info(hass)
assert agent_info == snapshot
@pytest.mark.parametrize("agent_id", AGENT_ID_OPTIONS)
async def test_prepare_agent(
hass: HomeAssistant,
init_components,
agent_id: str,
) -> None:
"""Test prepare agent."""
with patch(
"homeassistant.components.conversation.default_agent.DefaultAgent.async_prepare"
) as mock_prepare:
await conversation.async_prepare_agent(hass, agent_id, "en")
assert len(mock_prepare.mock_calls) == 1