mirror of
https://github.com/home-assistant/core.git
synced 2026-07-06 16:41:27 +02:00
24774798f1
Co-authored-by: Claude <noreply@anthropic.com>
173 lines
6.4 KiB
Python
173 lines
6.4 KiB
Python
"""Tests for the calendar LLM tools platform."""
|
|
|
|
from datetime import timedelta
|
|
|
|
from freezegun import freeze_time
|
|
import pytest
|
|
|
|
from homeassistant.components import calendar, llm as llm_component
|
|
from homeassistant.components.calendar import llm as calendar_llm
|
|
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
|
|
from homeassistant.core import Context, HomeAssistant, SupportsResponse
|
|
from homeassistant.helpers import entity_registry as er, llm
|
|
from homeassistant.setup import async_setup_component
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
from tests.common import async_mock_service
|
|
|
|
ENTITY_ID = "calendar.test_calendar"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
async def setup_integrations(hass: HomeAssistant) -> None:
|
|
"""Set up the integrations and expose a calendar."""
|
|
assert await async_setup_component(hass, "homeassistant", {})
|
|
assert await async_setup_component(hass, "calendar", {})
|
|
assert await async_setup_component(hass, "llm", {})
|
|
hass.states.async_set(ENTITY_ID, "on", {"friendly_name": "Mock Calendar Name"})
|
|
async_expose_entity(hass, "conversation", ENTITY_ID, True)
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
def _llm_context() -> llm.LLMContext:
|
|
"""Return an LLM context for the conversation assistant."""
|
|
return llm.LLMContext(
|
|
platform="test_platform",
|
|
context=Context(),
|
|
language="*",
|
|
assistant="conversation",
|
|
device_id=None,
|
|
)
|
|
|
|
|
|
async def test_get_tools_no_exposed_calendar(hass: HomeAssistant) -> None:
|
|
"""Test no calendar tool is offered when no calendar is exposed."""
|
|
async_expose_entity(hass, "conversation", ENTITY_ID, False)
|
|
result = await llm_component.async_get_tools(hass, _llm_context(), "assist")
|
|
assert "calendar_get_events" not in [tool.name for tool in result.tools]
|
|
assert calendar_llm.async_get_tools(hass, _llm_context(), "assist") is None
|
|
|
|
|
|
async def test_no_tools_for_other_api(hass: HomeAssistant) -> None:
|
|
"""Test the platform returns None for an unsupported API."""
|
|
assert calendar_llm.async_get_tools(hass, _llm_context(), "other") is None
|
|
|
|
|
|
async def test_calendar_get_events_tool(hass: HomeAssistant) -> None:
|
|
"""Test the calendar get events tool is exposed and works via the platform."""
|
|
llm_context = _llm_context()
|
|
result = await llm_component.async_get_tools(hass, llm_context, "assist")
|
|
tool = next(
|
|
(tool for tool in result.tools if tool.name == "calendar_get_events"), None
|
|
)
|
|
assert tool is not None
|
|
assert tool.parameters.schema["calendar"].container == ["Mock Calendar Name"]
|
|
|
|
calls = async_mock_service(
|
|
hass,
|
|
domain=calendar.DOMAIN,
|
|
service=calendar.SERVICE_GET_EVENTS,
|
|
schema=calendar.SERVICE_GET_EVENTS_SCHEMA,
|
|
response={
|
|
ENTITY_ID: {
|
|
"events": [
|
|
{
|
|
"start": "2025-09-17",
|
|
"end": "2025-09-18",
|
|
"summary": "Home Assistant 12th birthday",
|
|
"description": "",
|
|
},
|
|
{
|
|
"start": "2025-09-17T14:00:00-05:00",
|
|
"end": "2025-09-18T15:00:00-05:00",
|
|
"summary": "Champagne",
|
|
"description": "",
|
|
},
|
|
]
|
|
}
|
|
},
|
|
supports_response=SupportsResponse.ONLY,
|
|
)
|
|
|
|
tool_input = llm.ToolInput(
|
|
tool_name="calendar_get_events",
|
|
tool_args={"calendar": "Mock Calendar Name", "range": "today"},
|
|
)
|
|
now = dt_util.now()
|
|
with freeze_time(now):
|
|
response = await tool.async_call(hass, tool_input, llm_context)
|
|
|
|
assert len(calls) == 1
|
|
call = calls[0]
|
|
assert call.domain == calendar.DOMAIN
|
|
assert call.service == calendar.SERVICE_GET_EVENTS
|
|
assert call.data == {
|
|
"entity_id": [ENTITY_ID],
|
|
"start_date_time": now,
|
|
"end_date_time": dt_util.start_of_local_day(now) + timedelta(days=1),
|
|
}
|
|
|
|
assert response == {
|
|
"success": True,
|
|
"result": [
|
|
{
|
|
"start": "2025-09-17",
|
|
"end": "2025-09-18",
|
|
"summary": "Home Assistant 12th birthday",
|
|
"description": "",
|
|
"all_day": True,
|
|
},
|
|
{
|
|
"start": "2025-09-17T14:00:00-05:00",
|
|
"end": "2025-09-18T15:00:00-05:00",
|
|
"summary": "Champagne",
|
|
"description": "",
|
|
},
|
|
],
|
|
}
|
|
|
|
# The "week" range searches seven days out.
|
|
calls.clear()
|
|
tool_input.tool_args["range"] = "week"
|
|
with freeze_time(now):
|
|
await tool.async_call(hass, tool_input, llm_context)
|
|
assert call.domain == calendar.DOMAIN
|
|
assert calls[0].data["end_date_time"] == (
|
|
dt_util.start_of_local_day(now) + timedelta(days=7)
|
|
)
|
|
|
|
|
|
async def test_calendar_get_events_tool_not_found(hass: HomeAssistant) -> None:
|
|
"""Test the tool reports when the requested calendar no longer matches."""
|
|
llm_context = _llm_context()
|
|
result = await llm_component.async_get_tools(hass, llm_context, "assist")
|
|
tool = next(tool for tool in result.tools if tool.name == "calendar_get_events")
|
|
|
|
# Unexpose after the tool (and its calendar enum) was built, so the call-time
|
|
# match no longer finds the calendar.
|
|
async_expose_entity(hass, "conversation", ENTITY_ID, False)
|
|
response = await tool.async_call(
|
|
hass,
|
|
llm.ToolInput(
|
|
"calendar_get_events", {"calendar": "Mock Calendar Name", "range": "today"}
|
|
),
|
|
llm_context,
|
|
)
|
|
assert response == {"success": False, "error": "Calendar not found"}
|
|
|
|
|
|
async def test_calendar_get_events_tool_uses_aliases(
|
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
|
) -> None:
|
|
"""Test exposed calendar aliases are offered as valid tool values."""
|
|
entry = entity_registry.async_get_or_create(
|
|
"calendar", "test", "aliased", suggested_object_id="aliased"
|
|
)
|
|
entity_registry.async_update_entity(entry.entity_id, aliases={"Family Calendar"})
|
|
hass.states.async_set(entry.entity_id, "on")
|
|
async_expose_entity(hass, "conversation", entry.entity_id, True)
|
|
|
|
result = await llm_component.async_get_tools(hass, _llm_context(), "assist")
|
|
tool = next(tool for tool in result.tools if tool.name == "calendar_get_events")
|
|
assert "Family Calendar" in tool.parameters.schema["calendar"].container
|