diff --git a/kasa/smart/modules/autooff.py b/kasa/smart/modules/autooff.py index 385364fa..684a2c51 100644 --- a/kasa/smart/modules/autooff.py +++ b/kasa/smart/modules/autooff.py @@ -2,14 +2,13 @@ from __future__ import annotations +import logging from datetime import datetime, timedelta -from typing import TYPE_CHECKING from ...feature import Feature from ..smartmodule import SmartModule -if TYPE_CHECKING: - from ..smartdevice import SmartDevice +_LOGGER = logging.getLogger(__name__) class AutoOff(SmartModule): @@ -18,11 +17,17 @@ class AutoOff(SmartModule): REQUIRED_COMPONENT = "auto_off" QUERY_GETTER_NAME = "get_auto_off_config" - def __init__(self, device: SmartDevice, module: str): - super().__init__(device, module) + def _initialize_features(self): + """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( Feature( - device, + self._device, id="auto_off_enabled", name="Auto off enabled", container=self, @@ -33,7 +38,7 @@ class AutoOff(SmartModule): ) self._add_feature( Feature( - device, + self._device, id="auto_off_minutes", name="Auto off minutes", container=self, @@ -44,7 +49,7 @@ class AutoOff(SmartModule): ) self._add_feature( Feature( - device, + self._device, id="auto_off_at", name="Auto off at", container=self, diff --git a/kasa/tests/smart/modules/test_autooff.py b/kasa/tests/smart/modules/test_autooff.py new file mode 100644 index 00000000..c44617a7 --- /dev/null +++ b/kasa/tests/smart/modules/test_autooff.py @@ -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