Compare commits

...

1 Commits

Author SHA1 Message Date
Erik
edaeea88ad Set up calendar frontend resources when first platform is set up 2026-03-30 16:27:14 +02:00
5 changed files with 143 additions and 13 deletions

View File

@@ -36,6 +36,9 @@ from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_track_point_in_time
from homeassistant.helpers.integration_platform import (
async_process_integration_platforms,
)
from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
@@ -312,14 +315,32 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
)
hass.http.register_view(CalendarListView(component))
hass.http.register_view(CalendarEventView(component))
frontend_loaded = False
frontend.async_register_built_in_panel(hass, "calendar", "calendar", "mdi:calendar")
@callback
def async_platform_loaded(
hass: HomeAssistant, integration_domain: str, platform: Any
) -> None:
"""Register frontend resources for calendar."""
nonlocal frontend_loaded
websocket_api.async_register_command(hass, handle_calendar_event_create)
websocket_api.async_register_command(hass, handle_calendar_event_delete)
websocket_api.async_register_command(hass, handle_calendar_event_update)
if frontend_loaded:
return
frontend_loaded = True
hass.http.register_view(CalendarListView(component))
hass.http.register_view(CalendarEventView(component))
frontend.async_register_built_in_panel(
hass, "calendar", "calendar", "mdi:calendar"
)
websocket_api.async_register_command(hass, handle_calendar_event_create)
websocket_api.async_register_command(hass, handle_calendar_event_delete)
websocket_api.async_register_command(hass, handle_calendar_event_update)
await async_process_integration_platforms(hass, DOMAIN, async_platform_loaded)
component.async_register_entity_service(
CREATE_EVENT_SERVICE,

View File

@@ -125,12 +125,12 @@ async def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
@pytest.fixture
def mock_setup_integration(
def mock_setup_config_entry_integration(
hass: HomeAssistant,
config_flow_fixture: None,
test_entities: list[CalendarEntity],
) -> None:
"""Fixture to set up a mock integration."""
"""Fixture to set up a mock integration with config entry."""
async def async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry

View File

@@ -7,7 +7,9 @@ from datetime import timedelta
from http import HTTPStatus
import re
from typing import Any
from unittest.mock import AsyncMock
from aiohttp.test_utils import TestClient
from freezegun import freeze_time
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -20,14 +22,17 @@ from homeassistant.components.calendar import (
CalendarEntity,
CalendarEntityDescription,
)
from homeassistant.const import CONF_PLATFORM
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceNotSupported
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.typing import UNDEFINED
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from .conftest import MockCalendarEntity, MockConfigEntry
from tests.common import MockPlatform, mock_platform
from tests.typing import ClientSessionGenerator, WebSocketGenerator
@@ -50,12 +55,22 @@ def mock_set_frozen_time(frozen_time: str | None) -> Generator[None]:
yield
@pytest.fixture(name="setup_platform", autouse=True)
async def mock_setup_platform(
@pytest.fixture(name="setup_calendar_integration")
async def mock_setup_calendar_integration(
hass: HomeAssistant,
set_time_zone: None,
frozen_time: str | None,
mock_setup_integration: None,
) -> None:
"""Fixture to setup the calendar integration."""
await async_setup_component(hass, DOMAIN, {})
@pytest.fixture(name="setup_calendar_config_entry_platform")
async def mock_setup_config_entry_platform(
hass: HomeAssistant,
set_time_zone: None,
frozen_time: str | None,
mock_setup_config_entry_integration: None,
config_entry: MockConfigEntry,
) -> None:
"""Fixture to setup platforms used in the test and fixtures are set up in the right order."""
@@ -63,6 +78,7 @@ async def mock_setup_platform(
await hass.async_block_till_done()
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
async def test_events_http_api(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
@@ -78,6 +94,7 @@ async def test_events_http_api(
assert events[0]["summary"] == "Future Event"
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
async def test_events_http_api_missing_fields(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
@@ -87,6 +104,7 @@ async def test_events_http_api_missing_fields(
assert response.status == HTTPStatus.BAD_REQUEST
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
async def test_events_http_api_error(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
@@ -106,6 +124,7 @@ async def test_events_http_api_error(
assert await response.json() == {"message": "Error reading events: Failure"}
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
async def test_events_http_api_dates_wrong_order(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
@@ -119,6 +138,7 @@ async def test_events_http_api_dates_wrong_order(
assert response.status == HTTPStatus.BAD_REQUEST
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
async def test_calendars_http_api(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
@@ -134,6 +154,7 @@ async def test_calendars_http_api(
]
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
@pytest.mark.parametrize(
("payload", "code"),
[
@@ -222,6 +243,7 @@ async def test_unsupported_websocket(
assert resp["error"].get("code") == code
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
async def test_unsupported_create_event_service(hass: HomeAssistant) -> None:
"""Test unsupported service call."""
with pytest.raises(
@@ -242,6 +264,7 @@ async def test_unsupported_create_event_service(hass: HomeAssistant) -> None:
)
@pytest.mark.usefixtures("setup_calendar_integration")
@pytest.mark.parametrize(
("date_fields", "expected_error", "error_match"),
[
@@ -417,6 +440,7 @@ async def test_create_event_service_invalid_params(
)
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
@pytest.mark.parametrize(
"frozen_time", ["2023-06-22 10:30:00+00:00"], ids=["frozen_time"]
)
@@ -477,6 +501,7 @@ async def test_list_events_service(
assert response == expected
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
@pytest.mark.parametrize(
("service"),
[
@@ -516,6 +541,7 @@ async def test_list_events_service_duration(
assert response == snapshot
@pytest.mark.usefixtures("setup_calendar_integration")
async def test_list_events_positive_duration(hass: HomeAssistant) -> None:
"""Test listing events requires a positive duration."""
with pytest.raises(vol.Invalid, match="should be positive"):
@@ -531,6 +557,7 @@ async def test_list_events_positive_duration(hass: HomeAssistant) -> None:
)
@pytest.mark.usefixtures("setup_calendar_integration")
async def test_list_events_exclusive_fields(hass: HomeAssistant) -> None:
"""Test listing events specifying fields that are exclusive."""
end = dt_util.now() + timedelta(days=1)
@@ -549,6 +576,7 @@ async def test_list_events_exclusive_fields(hass: HomeAssistant) -> None:
)
@pytest.mark.usefixtures("setup_calendar_integration")
async def test_list_events_missing_fields(hass: HomeAssistant) -> None:
"""Test listing events missing some required fields."""
with pytest.raises(vol.Invalid, match="at least one of"):
@@ -563,6 +591,7 @@ async def test_list_events_missing_fields(hass: HomeAssistant) -> None:
)
@pytest.mark.usefixtures("setup_calendar_integration")
@pytest.mark.parametrize(
"frozen_time", ["2023-06-22 10:30:00+00:00"], ids=["frozen_time"]
)
@@ -613,6 +642,7 @@ async def test_list_events_service_same_dates(
)
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
async def test_calendar_initial_color_valid(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
@@ -628,6 +658,7 @@ async def test_calendar_initial_color_valid(
assert entry.options.get(DOMAIN, {}).get("color") == "#FF0000"
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
@pytest.mark.parametrize(
"invalid_initial_color",
[
@@ -654,6 +685,7 @@ async def test_calendar_initial_color_invalid(
assert entity.get_initial_entity_options() is None
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
async def test_calendar_initial_color_none(
hass: HomeAssistant,
test_entities: list[MockCalendarEntity],
@@ -664,6 +696,7 @@ async def test_calendar_initial_color_none(
assert entity.get_initial_entity_options() is None
@pytest.mark.usefixtures("setup_calendar_config_entry_platform")
@pytest.mark.parametrize(
("description_color", "attr_color", "expected_color"),
[
@@ -715,3 +748,79 @@ async def test_calendar_initial_color_precedence(
entity = TestCalendarEntity(description_color, attr_color)
assert entity.initial_color == expected_color
async def test_services_registered_after_integration_setup(hass: HomeAssistant) -> None:
"""Test that services are registered after integration setup."""
assert DOMAIN not in hass.services.async_services()
await async_setup_component(hass, DOMAIN, {})
assert set(hass.services.async_services()[DOMAIN]) == {"create_event", "get_events"}
async def _assert_http_api_responses(
client: TestClient,
expected_status_calendar_list: HTTPStatus,
expected_status_calendar_event: HTTPStatus,
) -> None:
"""Assert that the HTTP API endpoints return the expected status."""
response = await client.get("/api/calendars")
assert response.status == expected_status_calendar_list
response = await client.get("/api/calendars/calendar.calendar_1")
assert response.status == expected_status_calendar_event
async def test_frontend_resources_registered_after_first_config_entry_setup(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
set_time_zone: None,
frozen_time: str | None,
mock_setup_config_entry_integration: None,
config_entry: MockConfigEntry,
) -> None:
"""Test that frontend resources are registered after the first config entry is set up."""
await async_setup_component(hass, "http", {})
client = await hass_client()
await _assert_http_api_responses(client, HTTPStatus.NOT_FOUND, HTTPStatus.NOT_FOUND)
assert "frontend_panels" not in hass.data
assert "websocket_api" not in hass.data
await async_setup_component(hass, DOMAIN, {})
await _assert_http_api_responses(client, HTTPStatus.NOT_FOUND, HTTPStatus.NOT_FOUND)
assert "frontend_panels" not in hass.data
assert "websocket_api" not in hass.data
assert await hass.config_entries.async_setup(config_entry.entry_id)
await _assert_http_api_responses(client, HTTPStatus.OK, HTTPStatus.BAD_REQUEST)
assert set(hass.data["frontend_panels"]) == {"calendar"}
assert set(hass.data["websocket_api"]) == {
"calendar/event/create",
"calendar/event/delete",
"calendar/event/update",
}
async def test_frontend_resources_registered_after_first_platform_setup(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
) -> None:
"""Test that frontend resources are registered after the first platform is set up."""
await async_setup_component(hass, "http", {})
client = await hass_client()
await _assert_http_api_responses(client, HTTPStatus.NOT_FOUND, HTTPStatus.NOT_FOUND)
assert "frontend_panels" not in hass.data
assert "websocket_api" not in hass.data
mock_platform(
hass,
f"test.{DOMAIN}",
MockPlatform(async_setup_platform=AsyncMock()),
)
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
await _assert_http_api_responses(client, HTTPStatus.OK, HTTPStatus.BAD_REQUEST)
assert set(hass.data["frontend_panels"]) == {"calendar"}
assert set(hass.data["websocket_api"]) == {
"calendar/event/create",
"calendar/event/delete",
"calendar/event/update",
}

View File

@@ -19,7 +19,7 @@ async def mock_setup_dependencies(
recorder_mock: Recorder,
hass: HomeAssistant,
set_time_zone: None,
mock_setup_integration: None,
mock_setup_config_entry_integration: None,
config_entry: MockConfigEntry,
) -> None:
"""Fixture that ensures the recorder is setup in the right order."""

View File

@@ -279,7 +279,7 @@ def mock_test_entity(test_entities: list[MockCalendarEntity]) -> MockCalendarEnt
@pytest.fixture(name="setup_platform", autouse=True)
async def mock_setup_platform(
hass: HomeAssistant,
mock_setup_integration: None,
mock_setup_config_entry_integration: None,
config_entry: MockConfigEntry,
) -> None:
"""Fixture to setup platforms used in the test."""