mirror of
https://github.com/home-assistant/core.git
synced 2026-03-31 20:16:00 +00:00
Compare commits
4 Commits
python-3.1
...
setup_cale
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce1e6e02f6 | ||
|
|
69dcac1a19 | ||
|
|
4bc29274c7 | ||
|
|
cfc353be69 |
@@ -37,7 +37,7 @@ 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.template import DATE_STR_FORMAT
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.json import JsonValueType
|
||||
|
||||
@@ -308,19 +308,10 @@ SERVICE_GET_EVENTS_SCHEMA: Final = vol.All(
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Track states and offer events for calendars."""
|
||||
component = hass.data[DATA_COMPONENT] = EntityComponent[CalendarEntity](
|
||||
component = hass.data[DATA_COMPONENT] = CalendarEntityComponent(
|
||||
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
component.async_register_entity_service(
|
||||
CREATE_EVENT_SERVICE,
|
||||
CREATE_EVENT_SCHEMA,
|
||||
@@ -667,6 +658,53 @@ class CalendarEntity(Entity):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class CalendarEntityComponent(EntityComponent[CalendarEntity]):
|
||||
"""Calendar entity component.
|
||||
|
||||
Sets up frontend resources and websocket API when the first platform is added.
|
||||
"""
|
||||
|
||||
_frontend_loaded: bool = False
|
||||
|
||||
async def async_setup_entry(self, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up a config entry."""
|
||||
result = await super().async_setup_entry(config_entry)
|
||||
|
||||
if not self._frontend_loaded:
|
||||
self._register_frontend_resources()
|
||||
|
||||
return result
|
||||
|
||||
async def async_setup_platform(
|
||||
self,
|
||||
platform_type: str,
|
||||
platform_config: ConfigType,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up a platform for this component."""
|
||||
await super().async_setup_platform(
|
||||
platform_type, platform_config, discovery_info
|
||||
)
|
||||
|
||||
if not self._frontend_loaded:
|
||||
self._register_frontend_resources()
|
||||
|
||||
def _register_frontend_resources(self) -> None:
|
||||
"""Register frontend resources for calendar."""
|
||||
self._frontend_loaded = True
|
||||
|
||||
self.hass.http.register_view(CalendarListView(self))
|
||||
self.hass.http.register_view(CalendarEventView(self))
|
||||
|
||||
frontend.async_register_built_in_panel(
|
||||
self.hass, "calendar", "calendar", "mdi:calendar"
|
||||
)
|
||||
|
||||
websocket_api.async_register_command(self.hass, handle_calendar_event_create)
|
||||
websocket_api.async_register_command(self.hass, handle_calendar_event_delete)
|
||||
websocket_api.async_register_command(self.hass, handle_calendar_event_update)
|
||||
|
||||
|
||||
class CalendarEventView(http.HomeAssistantView):
|
||||
"""View to retrieve calendar content."""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user