Initialize autooff features only when data is available (#933)

For power strips, the autooff data needs to be requested from the
children.
Until we do that, we should not create these features to avoid crashing
during switch platform initialization.

This also ports the module to use `_initialize_features` and add tests.
This commit is contained in:
Teemu R 2024-05-24 19:39:10 +02:00 committed by GitHub
parent b217811096
commit 767156421b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 116 additions and 8 deletions

View File

@ -2,14 +2,13 @@
from __future__ import annotations from __future__ import annotations
import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import TYPE_CHECKING
from ...feature import Feature from ...feature import Feature
from ..smartmodule import SmartModule from ..smartmodule import SmartModule
if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__)
from ..smartdevice import SmartDevice
class AutoOff(SmartModule): class AutoOff(SmartModule):
@ -18,11 +17,17 @@ class AutoOff(SmartModule):
REQUIRED_COMPONENT = "auto_off" REQUIRED_COMPONENT = "auto_off"
QUERY_GETTER_NAME = "get_auto_off_config" QUERY_GETTER_NAME = "get_auto_off_config"
def __init__(self, device: SmartDevice, module: str): def _initialize_features(self):
super().__init__(device, module) """Initialize features after the initial update."""
if not isinstance(self.data, dict):
_LOGGER.warning(
"No data available for module, skipping %s: %s", self, self.data
)
return
self._add_feature( self._add_feature(
Feature( Feature(
device, self._device,
id="auto_off_enabled", id="auto_off_enabled",
name="Auto off enabled", name="Auto off enabled",
container=self, container=self,
@ -33,7 +38,7 @@ class AutoOff(SmartModule):
) )
self._add_feature( self._add_feature(
Feature( Feature(
device, self._device,
id="auto_off_minutes", id="auto_off_minutes",
name="Auto off minutes", name="Auto off minutes",
container=self, container=self,
@ -44,7 +49,7 @@ class AutoOff(SmartModule):
) )
self._add_feature( self._add_feature(
Feature( Feature(
device, self._device,
id="auto_off_at", id="auto_off_at",
name="Auto off at", name="Auto off at",
container=self, container=self,

View File

@ -0,0 +1,103 @@
from __future__ import annotations
import sys
from datetime import datetime
from typing import Optional
import pytest
from pytest_mock import MockerFixture
from kasa import Module
from kasa.smart import SmartDevice
from kasa.tests.device_fixtures import parametrize
autooff = parametrize(
"has autooff", component_filter="auto_off", protocol_filter={"SMART"}
)
@autooff
@pytest.mark.parametrize(
"feature, prop_name, type",
[
("auto_off_enabled", "enabled", bool),
("auto_off_minutes", "delay", int),
("auto_off_at", "auto_off_at", Optional[datetime]),
],
)
@pytest.mark.skipif(
sys.version_info < (3, 10),
reason="Subscripted generics cannot be used with class and instance checks",
)
async def test_autooff_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
):
"""Test that features are registered and work as expected."""
autooff = dev.modules.get(Module.AutoOff)
assert autooff is not None
prop = getattr(autooff, prop_name)
assert isinstance(prop, type)
feat = dev.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)
@autooff
async def test_settings(dev: SmartDevice, mocker: MockerFixture):
"""Test autooff settings."""
autooff = dev.modules.get(Module.AutoOff)
assert autooff
enabled = dev.features["auto_off_enabled"]
assert autooff.enabled == enabled.value
delay = dev.features["auto_off_minutes"]
assert autooff.delay == delay.value
call = mocker.spy(autooff, "call")
new_state = True
await autooff.set_enabled(new_state)
call.assert_called_with(
"set_auto_off_config", {"enable": new_state, "delay_min": delay.value}
)
call.reset_mock()
await dev.update()
new_delay = 123
await autooff.set_delay(new_delay)
call.assert_called_with(
"set_auto_off_config", {"enable": new_state, "delay_min": new_delay}
)
await dev.update()
assert autooff.enabled == new_state
assert autooff.delay == new_delay
@autooff
@pytest.mark.parametrize("is_timer_active", [True, False])
async def test_auto_off_at(
dev: SmartDevice, mocker: MockerFixture, is_timer_active: bool
):
"""Test auto-off at sensor."""
autooff = dev.modules.get(Module.AutoOff)
assert autooff
autooff_at = dev.features["auto_off_at"]
mocker.patch.object(
type(autooff),
"is_timer_active",
new_callable=mocker.PropertyMock,
return_value=is_timer_active,
)
if is_timer_active:
assert isinstance(autooff_at.value, datetime)
else:
assert autooff_at.value is None