231 lines
7.5 KiB
Python
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
|