mirror of
https://github.com/home-assistant/core.git
synced 2026-08-31 02:24:53 -05:00
Add time platform to Vistapool (#177749)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.NUMBER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.TIME,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,14 @@
|
||||
"uv": {
|
||||
"default": "mdi:weather-sunny-alert"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"filtration_interval_end": {
|
||||
"default": "mdi:clock-end"
|
||||
},
|
||||
"filtration_interval_start": {
|
||||
"default": "mdi:clock-start"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +207,14 @@
|
||||
"uv": {
|
||||
"name": "UV"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"filtration_interval_end": {
|
||||
"name": "Filtration interval {number} end"
|
||||
},
|
||||
"filtration_interval_start": {
|
||||
"name": "Filtration interval {number} start"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Vistapool Time entities."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import time
|
||||
from typing import override
|
||||
|
||||
from aioaquarite import AquariteError
|
||||
|
||||
from homeassistant.components.time import TimeEntity, TimeEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import VistapoolConfigEntry
|
||||
from .const import DOMAIN, SIGNAL_NEW_POOL
|
||||
from .coordinator import VistapoolDataUpdateCoordinator
|
||||
from .entity import VistapoolEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_SECONDS_PER_HOUR = 3600
|
||||
_SECONDS_PER_MINUTE = 60
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class VistapoolTimeEntityDescription(TimeEntityDescription):
|
||||
"""Describes a Vistapool time entity."""
|
||||
|
||||
value_path: str
|
||||
translation_placeholders: dict[str, str]
|
||||
|
||||
|
||||
TIME_DESCRIPTIONS: tuple[VistapoolTimeEntityDescription, ...] = tuple(
|
||||
VistapoolTimeEntityDescription(
|
||||
key=f"filtration_interval_{interval}_{bound}",
|
||||
translation_key=f"filtration_interval_{bound}",
|
||||
translation_placeholders={"number": str(interval)},
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_path=f"filtration.interval{interval}.{api_field}",
|
||||
)
|
||||
for interval in (1, 2, 3)
|
||||
for bound, api_field in (("start", "from"), ("end", "to"))
|
||||
)
|
||||
|
||||
|
||||
def _build_time_entities(
|
||||
coordinator: VistapoolDataUpdateCoordinator,
|
||||
) -> list[TimeEntity]:
|
||||
"""Build the time entities for a single pool."""
|
||||
return [
|
||||
VistapoolTime(coordinator, description) for description in TIME_DESCRIPTIONS
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: VistapoolConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Vistapool time entities for every pool on the account."""
|
||||
entities: list[TimeEntity] = []
|
||||
for coordinator in entry.runtime_data.coordinators.values():
|
||||
entities.extend(_build_time_entities(coordinator))
|
||||
async_add_entities(entities)
|
||||
|
||||
@callback
|
||||
def _async_add_pool(coordinator: VistapoolDataUpdateCoordinator) -> None:
|
||||
async_add_entities(_build_time_entities(coordinator))
|
||||
|
||||
entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"{SIGNAL_NEW_POOL}_{entry.entry_id}", _async_add_pool
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class VistapoolTime(VistapoolEntity, TimeEntity):
|
||||
"""Generic Vistapool time driven by an entity description."""
|
||||
|
||||
entity_description: VistapoolTimeEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: VistapoolDataUpdateCoordinator,
|
||||
description: VistapoolTimeEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the time entity."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = self.build_unique_id(description.key)
|
||||
self._attr_translation_placeholders = description.translation_placeholders
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_value(self) -> time | None:
|
||||
"""Return the interval bound as a time, decoded from seconds since midnight."""
|
||||
raw = self.coordinator.get_value(self.entity_description.value_path)
|
||||
if raw is None:
|
||||
return None
|
||||
try:
|
||||
seconds = int(raw)
|
||||
return time(
|
||||
seconds // _SECONDS_PER_HOUR,
|
||||
(seconds % _SECONDS_PER_HOUR) // _SECONDS_PER_MINUTE,
|
||||
seconds % _SECONDS_PER_MINUTE,
|
||||
)
|
||||
except TypeError, ValueError:
|
||||
# Also covers out-of-range values (negative or >= 24h), which make
|
||||
# the time() constructor raise instead of silently wrapping.
|
||||
return None
|
||||
|
||||
@override
|
||||
async def async_set_value(self, value: time) -> None:
|
||||
"""Send the interval bound to the controller as seconds since midnight."""
|
||||
seconds = (
|
||||
value.hour * _SECONDS_PER_HOUR
|
||||
+ value.minute * _SECONDS_PER_MINUTE
|
||||
+ value.second
|
||||
)
|
||||
try:
|
||||
await self.coordinator.api.set_value(
|
||||
self.coordinator.pool_id,
|
||||
self.entity_description.value_path,
|
||||
seconds,
|
||||
)
|
||||
except AquariteError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="set_failed",
|
||||
translation_placeholders={"entity": self.entity_id},
|
||||
) from err
|
||||
self.coordinator.apply_optimistic(self.entity_description.value_path, seconds)
|
||||
@@ -0,0 +1,301 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_1_end-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_1_end',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Filtration interval 1 end',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Filtration interval 1 end',
|
||||
'platform': 'vistapool',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'filtration_interval_end',
|
||||
'unique_id': 'ABCDEF1234567890-filtration_interval_1_end',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_1_end-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'My Pool Filtration interval 1 end',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_1_end',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '10:00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_1_start-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_1_start',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Filtration interval 1 start',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Filtration interval 1 start',
|
||||
'platform': 'vistapool',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'filtration_interval_start',
|
||||
'unique_id': 'ABCDEF1234567890-filtration_interval_1_start',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_1_start-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'My Pool Filtration interval 1 start',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_1_start',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '08:00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_2_end-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_2_end',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Filtration interval 2 end',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Filtration interval 2 end',
|
||||
'platform': 'vistapool',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'filtration_interval_end',
|
||||
'unique_id': 'ABCDEF1234567890-filtration_interval_2_end',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_2_end-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'My Pool Filtration interval 2 end',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_2_end',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '14:00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_2_start-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_2_start',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Filtration interval 2 start',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Filtration interval 2 start',
|
||||
'platform': 'vistapool',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'filtration_interval_start',
|
||||
'unique_id': 'ABCDEF1234567890-filtration_interval_2_start',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_2_start-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'My Pool Filtration interval 2 start',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_2_start',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '13:00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_3_end-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_3_end',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Filtration interval 3 end',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Filtration interval 3 end',
|
||||
'platform': 'vistapool',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'filtration_interval_end',
|
||||
'unique_id': 'ABCDEF1234567890-filtration_interval_3_end',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_3_end-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'My Pool Filtration interval 3 end',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_3_end',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '19:30:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_3_start-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_3_start',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Filtration interval 3 start',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Filtration interval 3 start',
|
||||
'platform': 'vistapool',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'filtration_interval_start',
|
||||
'unique_id': 'ABCDEF1234567890-filtration_interval_3_start',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[time.my_pool_filtration_interval_3_start-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'My Pool Filtration interval 3 start',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.my_pool_filtration_interval_3_start',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '19:00:00',
|
||||
})
|
||||
# ---
|
||||
@@ -0,0 +1,179 @@
|
||||
"""Tests for the Vistapool time platform."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from aioaquarite import AquariteError
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.time import (
|
||||
ATTR_TIME,
|
||||
DOMAIN as TIME_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _only_time_platform() -> Generator[None]:
|
||||
"""Restrict integration setup to the time platform for these tests."""
|
||||
with patch("homeassistant.components.vistapool.PLATFORMS", [Platform.TIME]):
|
||||
yield
|
||||
|
||||
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_vistapool_client: AsyncMock,
|
||||
mock_pool_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test time entities for the default fixture."""
|
||||
mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_time_decodes_seconds_since_midnight(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_vistapool_client: AsyncMock,
|
||||
mock_pool_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test the stored seconds-since-midnight are decoded to a time."""
|
||||
mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fixture stores interval1 from=28800 (08:00) and to=36000 (10:00).
|
||||
assert hass.states.get("time.my_pool_filtration_interval_1_start").state == (
|
||||
"08:00:00"
|
||||
)
|
||||
assert hass.states.get("time.my_pool_filtration_interval_1_end").state == "10:00:00"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw_value",
|
||||
[
|
||||
pytest.param("garbage", id="non_numeric"),
|
||||
pytest.param(None, id="missing"),
|
||||
pytest.param(86400, id="out_of_range_high"),
|
||||
pytest.param(-60, id="negative"),
|
||||
],
|
||||
)
|
||||
async def test_time_native_value_unknown_when_unparsable(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_vistapool_client: AsyncMock,
|
||||
raw_value: Any,
|
||||
) -> None:
|
||||
"""Test an unparsable or out-of-range raw value yields an unknown state."""
|
||||
mock_vistapool_client.fetch_pool_data.return_value = {
|
||||
"main": {"version": 1},
|
||||
"filtration": {"interval1": {"from": raw_value}},
|
||||
}
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
hass.states.get("time.my_pool_filtration_interval_1_start").state == "unknown"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "time_value", "expected_path", "expected_seconds"),
|
||||
[
|
||||
pytest.param(
|
||||
"time.my_pool_filtration_interval_1_start",
|
||||
"12:30:00",
|
||||
"filtration.interval1.from",
|
||||
45000,
|
||||
id="interval_1_start",
|
||||
),
|
||||
pytest.param(
|
||||
"time.my_pool_filtration_interval_2_end",
|
||||
"12:30:00",
|
||||
"filtration.interval2.to",
|
||||
45000,
|
||||
id="interval_2_end",
|
||||
),
|
||||
pytest.param(
|
||||
"time.my_pool_filtration_interval_3_start",
|
||||
"07:05:09",
|
||||
"filtration.interval3.from",
|
||||
25509,
|
||||
id="interval_3_start_with_seconds",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_time_set_value(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_vistapool_client: AsyncMock,
|
||||
mock_pool_data: dict[str, Any],
|
||||
entity_id: str,
|
||||
time_value: str,
|
||||
expected_path: str,
|
||||
expected_seconds: int,
|
||||
) -> None:
|
||||
"""Test set_value encodes the time as seconds since midnight at the right path."""
|
||||
mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
TIME_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_TIME: time_value},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_vistapool_client.set_value.assert_awaited_once_with(
|
||||
"ABCDEF1234567890", expected_path, expected_seconds
|
||||
)
|
||||
assert hass.states.get(entity_id).state == time_value
|
||||
|
||||
|
||||
async def test_time_set_value_raises_on_api_error(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_vistapool_client: AsyncMock,
|
||||
mock_pool_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test set_value re-raises as HomeAssistantError when the library fails."""
|
||||
mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
|
||||
mock_vistapool_client.set_value.side_effect = AquariteError("boom")
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with pytest.raises(HomeAssistantError) as excinfo:
|
||||
await hass.services.async_call(
|
||||
TIME_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "time.my_pool_filtration_interval_1_start",
|
||||
ATTR_TIME: "12:30:00",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert excinfo.value.translation_key == "set_failed"
|
||||
Reference in New Issue
Block a user