mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Add Rain Bird irrigation calendar (#87604)
* Initial version of a calendar for the rainbird integration * Improve calendar support * Revert changes to test fixtures * Address ruff error * Fix background task scheduling * Use pytest.mark.freezetime to move to test setup * Address PR feedback * Make refresh a member * Merge rainbird and calendar changes * Increase test coverage * Readability improvements * Simplify timezone handling
This commit is contained in:
parent
18f29993c5
commit
fa2d77407a
@ -10,10 +10,15 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONF_SERIAL_NUMBER
|
||||
from .coordinator import RainbirdUpdateCoordinator
|
||||
from .coordinator import RainbirdData
|
||||
|
||||
PLATFORMS = [Platform.SWITCH, Platform.SENSOR, Platform.BINARY_SENSOR, Platform.NUMBER]
|
||||
PLATFORMS = [
|
||||
Platform.SWITCH,
|
||||
Platform.SENSOR,
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.NUMBER,
|
||||
Platform.CALENDAR,
|
||||
]
|
||||
|
||||
|
||||
DOMAIN = "rainbird"
|
||||
@ -35,16 +40,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
model_info = await controller.get_model_and_version()
|
||||
except RainbirdApiException as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
coordinator = RainbirdUpdateCoordinator(
|
||||
hass,
|
||||
name=entry.title,
|
||||
controller=controller,
|
||||
serial_number=entry.data[CONF_SERIAL_NUMBER],
|
||||
model_info=model_info,
|
||||
)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
data = RainbirdData(hass, entry, controller, model_info)
|
||||
await data.coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = data
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
|
@ -31,7 +31,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entry for a Rain Bird binary_sensor."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id].coordinator
|
||||
async_add_entities([RainBirdSensor(coordinator, RAIN_SENSOR_ENTITY_DESCRIPTION)])
|
||||
|
||||
|
||||
|
118
homeassistant/components/rainbird/calendar.py
Normal file
118
homeassistant/components/rainbird/calendar.py
Normal file
@ -0,0 +1,118 @@
|
||||
"""Rain Bird irrigation calendar."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RainbirdScheduleUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entry for a Rain Bird irrigation calendar."""
|
||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
if not data.model_info.model_info.max_programs:
|
||||
return
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
RainBirdCalendarEntity(
|
||||
data.schedule_coordinator,
|
||||
data.coordinator.serial_number,
|
||||
data.coordinator.device_info,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class RainBirdCalendarEntity(
|
||||
CoordinatorEntity[RainbirdScheduleUpdateCoordinator], CalendarEntity
|
||||
):
|
||||
"""A calendar event entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_attr_icon = "mdi:sprinkler"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RainbirdScheduleUpdateCoordinator,
|
||||
serial_number: str,
|
||||
device_info: DeviceInfo,
|
||||
) -> None:
|
||||
"""Create the Calendar event device."""
|
||||
super().__init__(coordinator)
|
||||
self._event: CalendarEvent | None = None
|
||||
self._attr_unique_id = serial_number
|
||||
self._attr_device_info = device_info
|
||||
|
||||
@property
|
||||
def event(self) -> CalendarEvent | None:
|
||||
"""Return the next upcoming event."""
|
||||
schedule = self.coordinator.data
|
||||
if not schedule:
|
||||
return None
|
||||
cursor = schedule.timeline_tz(dt_util.DEFAULT_TIME_ZONE).active_after(
|
||||
dt_util.now()
|
||||
)
|
||||
program_event = next(cursor, None)
|
||||
if not program_event:
|
||||
return None
|
||||
return CalendarEvent(
|
||||
summary=program_event.program_id.name,
|
||||
start=dt_util.as_local(program_event.start),
|
||||
end=dt_util.as_local(program_event.end),
|
||||
rrule=program_event.rrule_str,
|
||||
)
|
||||
|
||||
async def async_get_events(
|
||||
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
||||
) -> list[CalendarEvent]:
|
||||
"""Get all events in a specific time frame."""
|
||||
schedule = self.coordinator.data
|
||||
if not schedule:
|
||||
raise HomeAssistantError(
|
||||
"Unable to get events: No data from controller yet"
|
||||
)
|
||||
cursor = schedule.timeline_tz(start_date.tzinfo).overlapping(
|
||||
start_date,
|
||||
end_date,
|
||||
)
|
||||
return [
|
||||
CalendarEvent(
|
||||
summary=program_event.program_id.name,
|
||||
start=dt_util.as_local(program_event.start),
|
||||
end=dt_util.as_local(program_event.end),
|
||||
rrule=program_event.rrule_str,
|
||||
)
|
||||
for program_event in cursor
|
||||
]
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""When entity is added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
# We do not ask for an update with async_add_entities()
|
||||
# because it will update disabled entities. This is started as a
|
||||
# task to let it sync in the background without blocking startup
|
||||
self.coordinator.config_entry.async_create_background_task(
|
||||
self.hass,
|
||||
self.coordinator.async_request_refresh(),
|
||||
"rainbird.calendar-refresh",
|
||||
)
|
@ -5,23 +5,29 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
from functools import cached_property
|
||||
import logging
|
||||
from typing import TypeVar
|
||||
|
||||
import async_timeout
|
||||
from pyrainbird.async_client import (
|
||||
AsyncRainbirdController,
|
||||
RainbirdApiException,
|
||||
RainbirdDeviceBusyException,
|
||||
)
|
||||
from pyrainbird.data import ModelAndVersion
|
||||
from pyrainbird.data import ModelAndVersion, Schedule
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, TIMEOUT_SECONDS
|
||||
from .const import CONF_SERIAL_NUMBER, DOMAIN, MANUFACTURER, TIMEOUT_SECONDS
|
||||
|
||||
UPDATE_INTERVAL = datetime.timedelta(minutes=1)
|
||||
# The calendar data requires RPCs for each program/zone, and the data rarely
|
||||
# changes, so we refresh it less often.
|
||||
CALENDAR_UPDATE_INTERVAL = datetime.timedelta(minutes=15)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -49,7 +55,7 @@ class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]):
|
||||
serial_number: str,
|
||||
model_info: ModelAndVersion,
|
||||
) -> None:
|
||||
"""Initialize ZoneStateUpdateCoordinator."""
|
||||
"""Initialize RainbirdUpdateCoordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
@ -108,3 +114,66 @@ class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]):
|
||||
rain=rain,
|
||||
rain_delay=rain_delay,
|
||||
)
|
||||
|
||||
|
||||
class RainbirdScheduleUpdateCoordinator(DataUpdateCoordinator[Schedule]):
|
||||
"""Coordinator for rainbird irrigation schedule calls."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
name: str,
|
||||
controller: AsyncRainbirdController,
|
||||
) -> None:
|
||||
"""Initialize ZoneStateUpdateCoordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=name,
|
||||
update_method=self._async_update_data,
|
||||
update_interval=CALENDAR_UPDATE_INTERVAL,
|
||||
)
|
||||
self._controller = controller
|
||||
|
||||
async def _async_update_data(self) -> Schedule:
|
||||
"""Fetch data from Rain Bird device."""
|
||||
try:
|
||||
async with async_timeout.timeout(TIMEOUT_SECONDS):
|
||||
return await self._controller.get_schedule()
|
||||
except RainbirdApiException as err:
|
||||
raise UpdateFailed(f"Error communicating with Device: {err}") from err
|
||||
|
||||
|
||||
@dataclass
|
||||
class RainbirdData:
|
||||
"""Holder for shared integration data.
|
||||
|
||||
The coordinators are lazy since they may only be used by some platforms when needed.
|
||||
"""
|
||||
|
||||
hass: HomeAssistant
|
||||
entry: ConfigEntry
|
||||
controller: AsyncRainbirdController
|
||||
model_info: ModelAndVersion
|
||||
|
||||
@cached_property
|
||||
def coordinator(self) -> RainbirdUpdateCoordinator:
|
||||
"""Return RainbirdUpdateCoordinator."""
|
||||
return RainbirdUpdateCoordinator(
|
||||
self.hass,
|
||||
name=self.entry.title,
|
||||
controller=self.controller,
|
||||
serial_number=self.entry.data[CONF_SERIAL_NUMBER],
|
||||
model_info=self.model_info,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def schedule_coordinator(self) -> RainbirdScheduleUpdateCoordinator:
|
||||
"""Return RainbirdScheduleUpdateCoordinator."""
|
||||
return RainbirdScheduleUpdateCoordinator(
|
||||
self.hass,
|
||||
name=f"{self.entry.title} Schedule",
|
||||
controller=self.controller,
|
||||
)
|
||||
|
@ -28,7 +28,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
RainDelayNumber(
|
||||
hass.data[DOMAIN][config_entry.entry_id],
|
||||
hass.data[DOMAIN][config_entry.entry_id].coordinator,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
@ -32,7 +32,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
RainBirdSensor(
|
||||
hass.data[DOMAIN][config_entry.entry_id],
|
||||
hass.data[DOMAIN][config_entry.entry_id].coordinator,
|
||||
RAIN_DELAY_ENTITY_DESCRIPTION,
|
||||
)
|
||||
]
|
||||
|
@ -33,7 +33,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entry for a Rain Bird irrigation switches."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id].coordinator
|
||||
async_add_entities(
|
||||
RainBirdSwitch(
|
||||
coordinator,
|
||||
|
@ -37,7 +37,7 @@ SERIAL_NUMBER = 0x12635436566
|
||||
SERIAL_RESPONSE = "850000012635436566"
|
||||
ZERO_SERIAL_RESPONSE = "850000000000000000"
|
||||
# Model and version command 0x82
|
||||
MODEL_AND_VERSION_RESPONSE = "820006090C"
|
||||
MODEL_AND_VERSION_RESPONSE = "820005090C" # ESP-TM2
|
||||
# Get available stations command 0x83
|
||||
AVAILABLE_STATIONS_RESPONSE = "83017F000000" # Mask for 7 zones
|
||||
EMPTY_STATIONS_RESPONSE = "830000000000"
|
||||
@ -184,8 +184,15 @@ def mock_rain_delay_response() -> str:
|
||||
return RAIN_DELAY_OFF
|
||||
|
||||
|
||||
@pytest.fixture(name="model_and_version_response")
|
||||
def mock_model_and_version_response() -> str:
|
||||
"""Mock response to return rain delay state."""
|
||||
return MODEL_AND_VERSION_RESPONSE
|
||||
|
||||
|
||||
@pytest.fixture(name="api_responses")
|
||||
def mock_api_responses(
|
||||
model_and_version_response: str,
|
||||
stations_response: str,
|
||||
zone_state_response: str,
|
||||
rain_response: str,
|
||||
@ -196,7 +203,7 @@ def mock_api_responses(
|
||||
These are returned in the order they are requested by the update coordinator.
|
||||
"""
|
||||
return [
|
||||
MODEL_AND_VERSION_RESPONSE,
|
||||
model_and_version_response,
|
||||
stations_response,
|
||||
zone_state_response,
|
||||
rain_response,
|
||||
|
272
tests/components/rainbird/test_calendar.py
Normal file
272
tests/components/rainbird/test_calendar.py
Normal file
@ -0,0 +1,272 @@
|
||||
"""Tests for rainbird calendar platform."""
|
||||
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
import datetime
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
import urllib
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .conftest import ComponentSetup, mock_response, mock_response_error
|
||||
|
||||
from tests.test_util.aiohttp import AiohttpClientMockResponse
|
||||
|
||||
TEST_ENTITY = "calendar.rain_bird_controller"
|
||||
GetEventsFn = Callable[[str, str], Awaitable[dict[str, Any]]]
|
||||
|
||||
SCHEDULE_RESPONSES = [
|
||||
# Current controller status
|
||||
"A0000000000000",
|
||||
# Per-program information
|
||||
"A00010060602006400", # CUSTOM: Monday & Tuesday
|
||||
"A00011110602006400",
|
||||
"A00012000300006400",
|
||||
# Start times per program
|
||||
"A0006000F0FFFFFFFFFFFF", # 4am
|
||||
"A00061FFFFFFFFFFFFFFFF",
|
||||
"A00062FFFFFFFFFFFFFFFF",
|
||||
# Run times for each zone
|
||||
"A00080001900000000001400000000", # zone1=25, zone2=20
|
||||
"A00081000700000000001400000000", # zone3=7, zone4=20
|
||||
"A00082000A00000000000000000000", # zone5=10
|
||||
"A00083000000000000000000000000",
|
||||
"A00084000000000000000000000000",
|
||||
"A00085000000000000000000000000",
|
||||
"A00086000000000000000000000000",
|
||||
"A00087000000000000000000000000",
|
||||
"A00088000000000000000000000000",
|
||||
"A00089000000000000000000000000",
|
||||
"A0008A000000000000000000000000",
|
||||
]
|
||||
|
||||
EMPTY_SCHEDULE_RESPONSES = [
|
||||
# Current controller status
|
||||
"A0000000000000",
|
||||
# Per-program information (ignored)
|
||||
"A00010000000000000",
|
||||
"A00011000000000000",
|
||||
"A00012000000000000",
|
||||
# Start times for each program (off)
|
||||
"A00060FFFFFFFFFFFFFFFF",
|
||||
"A00061FFFFFFFFFFFFFFFF",
|
||||
"A00062FFFFFFFFFFFFFFFF",
|
||||
# Run times for each zone
|
||||
"A00080000000000000000000000000",
|
||||
"A00081000000000000000000000000",
|
||||
"A00082000000000000000000000000",
|
||||
"A00083000000000000000000000000",
|
||||
"A00084000000000000000000000000",
|
||||
"A00085000000000000000000000000",
|
||||
"A00086000000000000000000000000",
|
||||
"A00087000000000000000000000000",
|
||||
"A00088000000000000000000000000",
|
||||
"A00089000000000000000000000000",
|
||||
"A0008A000000000000000000000000",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[str]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.CALENDAR]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def set_time_zone(hass: HomeAssistant):
|
||||
"""Set the time zone for the tests."""
|
||||
hass.config.set_time_zone("America/Regina")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_schedule_responses() -> list[str]:
|
||||
"""Fixture containing fake irrigation schedule."""
|
||||
return SCHEDULE_RESPONSES
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_insert_schedule_response(
|
||||
mock_schedule_responses: list[str], responses: list[AiohttpClientMockResponse]
|
||||
) -> None:
|
||||
"""Fixture to insert device responses for the irrigation schedule."""
|
||||
responses.extend(
|
||||
[mock_response(api_response) for api_response in mock_schedule_responses]
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="get_events")
|
||||
def get_events_fixture(
|
||||
hass_client: Callable[..., Awaitable[ClientSession]]
|
||||
) -> GetEventsFn:
|
||||
"""Fetch calendar events from the HTTP API."""
|
||||
|
||||
async def _fetch(start: str, end: str) -> list[dict[str, Any]]:
|
||||
client = await hass_client()
|
||||
response = await client.get(
|
||||
f"/api/calendars/{TEST_ENTITY}?start={urllib.parse.quote(start)}&end={urllib.parse.quote(end)}"
|
||||
)
|
||||
assert response.status == HTTPStatus.OK
|
||||
results = await response.json()
|
||||
return [{k: event[k] for k in {"summary", "start", "end"}} for event in results]
|
||||
|
||||
return _fetch
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2023-01-21 09:32:00")
|
||||
async def test_get_events(
|
||||
hass: HomeAssistant, setup_integration: ComponentSetup, get_events: GetEventsFn
|
||||
) -> None:
|
||||
"""Test calendar event fetching APIs."""
|
||||
|
||||
assert await setup_integration()
|
||||
|
||||
events = await get_events("2023-01-20T00:00:00Z", "2023-02-05T00:00:00Z")
|
||||
assert events == [
|
||||
# Monday
|
||||
{
|
||||
"summary": "PGM A",
|
||||
"start": {"dateTime": "2023-01-23T04:00:00-06:00"},
|
||||
"end": {"dateTime": "2023-01-23T05:22:00-06:00"},
|
||||
},
|
||||
# Tuesday
|
||||
{
|
||||
"summary": "PGM A",
|
||||
"start": {"dateTime": "2023-01-24T04:00:00-06:00"},
|
||||
"end": {"dateTime": "2023-01-24T05:22:00-06:00"},
|
||||
},
|
||||
# Monday
|
||||
{
|
||||
"summary": "PGM A",
|
||||
"start": {"dateTime": "2023-01-30T04:00:00-06:00"},
|
||||
"end": {"dateTime": "2023-01-30T05:22:00-06:00"},
|
||||
},
|
||||
# Tuesday
|
||||
{
|
||||
"summary": "PGM A",
|
||||
"start": {"dateTime": "2023-01-31T04:00:00-06:00"},
|
||||
"end": {"dateTime": "2023-01-31T05:22:00-06:00"},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("freeze_time", "expected_state"),
|
||||
[
|
||||
(
|
||||
datetime.datetime(2023, 1, 23, 3, 50, tzinfo=ZoneInfo("America/Regina")),
|
||||
"off",
|
||||
),
|
||||
(
|
||||
datetime.datetime(2023, 1, 23, 4, 30, tzinfo=ZoneInfo("America/Regina")),
|
||||
"on",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_event_state(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: ComponentSetup,
|
||||
get_events: GetEventsFn,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
freeze_time: datetime.datetime,
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test calendar upcoming event state."""
|
||||
freezer.move_to(freeze_time)
|
||||
|
||||
assert await setup_integration()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state is not None
|
||||
assert state.attributes == {
|
||||
"message": "PGM A",
|
||||
"start_time": "2023-01-23 04:00:00",
|
||||
"end_time": "2023-01-23 05:22:00",
|
||||
"all_day": False,
|
||||
"description": "",
|
||||
"location": "",
|
||||
"friendly_name": "Rain Bird Controller",
|
||||
"icon": "mdi:sprinkler",
|
||||
}
|
||||
assert state.state == expected_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model_and_version_response", "has_entity"),
|
||||
[
|
||||
("820005090C", True),
|
||||
("820006090C", False),
|
||||
],
|
||||
ids=("ESP-TM2", "ST8x-WiFi"),
|
||||
)
|
||||
async def test_calendar_not_supported_by_device(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: ComponentSetup,
|
||||
has_entity: bool,
|
||||
) -> None:
|
||||
"""Test calendar upcoming event state."""
|
||||
|
||||
assert await setup_integration()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert (state is not None) == has_entity
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mock_insert_schedule_response", [([None])] # Disable success responses
|
||||
)
|
||||
async def test_no_schedule(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: ComponentSetup,
|
||||
get_events: GetEventsFn,
|
||||
responses: list[AiohttpClientMockResponse],
|
||||
hass_client: Callable[..., Awaitable[ClientSession]],
|
||||
) -> None:
|
||||
"""Test calendar error when fetching the calendar."""
|
||||
responses.extend([mock_response_error(HTTPStatus.BAD_GATEWAY)]) # Arbitrary error
|
||||
|
||||
assert await setup_integration()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.state == "unavailable"
|
||||
assert state.attributes == {
|
||||
"friendly_name": "Rain Bird Controller",
|
||||
"icon": "mdi:sprinkler",
|
||||
}
|
||||
|
||||
client = await hass_client()
|
||||
response = await client.get(
|
||||
f"/api/calendars/{TEST_ENTITY}?start=2023-08-01&end=2023-08-02"
|
||||
)
|
||||
assert response.status == HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2023-01-21 09:32:00")
|
||||
@pytest.mark.parametrize(
|
||||
"mock_schedule_responses",
|
||||
[(EMPTY_SCHEDULE_RESPONSES)],
|
||||
)
|
||||
async def test_program_schedule_disabled(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: ComponentSetup,
|
||||
get_events: GetEventsFn,
|
||||
) -> None:
|
||||
"""Test calendar when the program is disabled with no upcoming events."""
|
||||
|
||||
assert await setup_integration()
|
||||
|
||||
events = await get_events("2023-01-20T00:00:00Z", "2023-02-05T00:00:00Z")
|
||||
assert events == []
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.state == "off"
|
||||
assert state.attributes == {
|
||||
"friendly_name": "Rain Bird Controller",
|
||||
"icon": "mdi:sprinkler",
|
||||
}
|
@ -73,7 +73,7 @@ async def test_set_value(
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, SERIAL_NUMBER)})
|
||||
assert device
|
||||
assert device.name == "Rain Bird Controller"
|
||||
assert device.model == "ST8x-WiFi"
|
||||
assert device.model == "ESP-TM2"
|
||||
assert device.sw_version == "9.12"
|
||||
|
||||
aioclient_mock.mock_calls.clear()
|
||||
|
@ -57,7 +57,6 @@ async def test_no_zones(
|
||||
async def test_zones(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: ComponentSetup,
|
||||
responses: list[AiohttpClientMockResponse],
|
||||
) -> None:
|
||||
"""Test switch platform with fake data that creates 7 zones with one enabled."""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user