Move tests folder to top level of project (#1242)

This commit is contained in:
Steven B.
2024-11-11 10:11:31 +00:00
committed by GitHub
parent e5dd874333
commit 32671da9e9
212 changed files with 97 additions and 76 deletions

View File

View File

@@ -0,0 +1,104 @@
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 ...device_fixtures import get_parent_and_child_modules, 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 = next(get_parent_and_child_modules(dev, Module.AutoOff))
assert autooff is not None
prop = getattr(autooff, prop_name)
assert isinstance(prop, type)
feat = autooff._device.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)
@autooff
async def test_settings(dev: SmartDevice, mocker: MockerFixture):
"""Test autooff settings."""
autooff = next(get_parent_and_child_modules(dev, Module.AutoOff))
assert autooff
enabled = autooff._device.features["auto_off_enabled"]
assert autooff.enabled == enabled.value
delay = autooff._device.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 = next(get_parent_and_child_modules(dev, Module.AutoOff))
assert autooff
autooff_at = autooff._device.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

View File

@@ -0,0 +1,44 @@
import pytest
from kasa import Module
from kasa.smart.modules import ChildProtection
from ...device_fixtures import parametrize
child_protection = parametrize(
"has child protection",
component_filter="child_protection",
protocol_filter={"SMART.CHILD"},
)
@child_protection
@pytest.mark.parametrize(
("feature", "prop_name", "type"),
[
("child_lock", "enabled", bool),
],
)
async def test_features(dev, feature, prop_name, type):
"""Test that features are registered and work as expected."""
protect: ChildProtection = dev.modules[Module.ChildProtection]
assert protect is not None
prop = getattr(protect, prop_name)
assert isinstance(prop, type)
feat = protect._device.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)
@child_protection
async def test_enabled(dev):
"""Test the API."""
protect: ChildProtection = dev.modules[Module.ChildProtection]
assert protect is not None
assert isinstance(protect.enabled, bool)
await protect.set_enabled(False)
await dev.update()
assert protect.enabled is False

View File

@@ -0,0 +1,29 @@
import pytest
from kasa import Module, SmartDevice
from ...device_fixtures import parametrize
contact = parametrize(
"is contact sensor", model_filter="T110", protocol_filter={"SMART.CHILD"}
)
@contact
@pytest.mark.parametrize(
("feature", "type"),
[
("is_open", bool),
],
)
async def test_contact_features(dev: SmartDevice, feature, type):
"""Test that features are registered and work as expected."""
contact = dev.modules.get(Module.ContactSensor)
assert contact is not None
prop = getattr(contact, feature)
assert isinstance(prop, type)
feat = dev.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)

View File

@@ -0,0 +1,84 @@
import pytest
from pytest_mock import MockerFixture
from kasa import Module
from kasa.smart import SmartDevice
from ...device_fixtures import get_parent_and_child_modules, parametrize
fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"SMART"})
@fan
async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
"""Test fan speed feature."""
fan = next(get_parent_and_child_modules(dev, Module.Fan))
assert fan
level_feature = fan._module_features["fan_speed_level"]
assert (
level_feature.minimum_value
<= level_feature.value
<= level_feature.maximum_value
)
call = mocker.spy(fan, "call")
await fan.set_fan_speed_level(3)
call.assert_called_with(
"set_device_info", {"device_on": True, "fan_speed_level": 3}
)
await dev.update()
assert fan.fan_speed_level == 3
assert level_feature.value == 3
@fan
async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
"""Test sleep mode feature."""
fan = next(get_parent_and_child_modules(dev, Module.Fan))
assert fan
sleep_feature = fan._module_features["fan_sleep_mode"]
assert isinstance(sleep_feature.value, bool)
call = mocker.spy(fan, "call")
await fan.set_sleep_mode(True)
call.assert_called_with("set_device_info", {"fan_sleep_mode_on": True})
await dev.update()
assert fan.sleep_mode is True
assert sleep_feature.value is True
@fan
async def test_fan_module(dev: SmartDevice, mocker: MockerFixture):
"""Test fan speed on device interface."""
assert isinstance(dev, SmartDevice)
fan = next(get_parent_and_child_modules(dev, Module.Fan))
assert fan
device = fan._device
await fan.set_fan_speed_level(1)
await dev.update()
assert fan.fan_speed_level == 1
assert device.is_on
# Check that if the device is off the speed level is 0.
await device.set_state(False)
await dev.update()
assert fan.fan_speed_level == 0
await fan.set_fan_speed_level(4)
await dev.update()
assert fan.fan_speed_level == 4
await fan.set_fan_speed_level(0)
await dev.update()
assert not device.is_on
with pytest.raises(ValueError, match="Invalid level"):
await fan.set_fan_speed_level(-1)
with pytest.raises(ValueError, match="Invalid level"):
await fan.set_fan_speed_level(5)

View File

@@ -0,0 +1,150 @@
from __future__ import annotations
import asyncio
import logging
from contextlib import nullcontext
from typing import TypedDict
import pytest
from pytest_mock import MockerFixture
from kasa import KasaException, Module
from kasa.smart import SmartDevice
from kasa.smart.modules.firmware import DownloadState
from ...device_fixtures import parametrize
firmware = parametrize(
"has firmware", component_filter="firmware", protocol_filter={"SMART"}
)
@firmware
@pytest.mark.parametrize(
("feature", "prop_name", "type", "required_version"),
[
("auto_update_enabled", "auto_update_enabled", bool, 2),
("update_available", "update_available", bool, 1),
("current_firmware_version", "current_firmware", str, 1),
("available_firmware_version", "latest_firmware", str, 1),
],
)
async def test_firmware_features(
dev: SmartDevice, feature, prop_name, type, required_version, mocker: MockerFixture
):
"""Test light effect."""
fw = dev.modules.get(Module.Firmware)
assert fw
assert fw.firmware_update_info is None
if not dev.is_cloud_connected:
pytest.skip("Device is not cloud connected, skipping test")
await fw.check_latest_firmware()
if fw.supported_version < required_version:
pytest.skip(f"Feature {feature} requires newer version")
prop = getattr(fw, prop_name)
assert isinstance(prop, type)
feat = dev.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)
@firmware
async def test_update_available_without_cloud(dev: SmartDevice):
"""Test that update_available returns None when disconnected."""
fw = dev.modules.get(Module.Firmware)
assert fw
assert fw.firmware_update_info is None
if dev.is_cloud_connected:
await fw.check_latest_firmware()
assert isinstance(fw.update_available, bool)
else:
assert fw.update_available is None
@firmware
@pytest.mark.parametrize(
("update_available", "expected_result"),
[
pytest.param(True, nullcontext(), id="available"),
pytest.param(False, pytest.raises(KasaException), id="not-available"),
],
)
async def test_firmware_update(
dev: SmartDevice,
mocker: MockerFixture,
caplog: pytest.LogCaptureFixture,
update_available,
expected_result,
):
"""Test updating firmware."""
caplog.set_level(logging.INFO)
if not dev.is_cloud_connected:
pytest.skip("Device is not cloud connected, skipping test")
fw = dev.modules.get(Module.Firmware)
assert fw
upgrade_time = 5
class Extras(TypedDict):
reboot_time: int
upgrade_time: int
auto_upgrade: bool
extras: Extras = {
"reboot_time": 5,
"upgrade_time": upgrade_time,
"auto_upgrade": False,
}
update_states = [
# Unknown 1
DownloadState(status=1, download_progress=0, **extras),
# Downloading
DownloadState(status=2, download_progress=10, **extras),
DownloadState(status=2, download_progress=100, **extras),
# Flashing
DownloadState(status=3, download_progress=100, **extras),
DownloadState(status=3, download_progress=100, **extras),
# Done
DownloadState(status=0, download_progress=100, **extras),
]
asyncio_sleep = asyncio.sleep
sleep = mocker.patch("asyncio.sleep")
mocker.patch.object(fw, "get_update_state", side_effect=update_states)
cb_mock = mocker.AsyncMock()
assert fw.firmware_update_info is None
with pytest.raises(KasaException):
await fw.update(progress_cb=cb_mock)
await fw.check_latest_firmware()
assert fw.firmware_update_info is not None
fw._firmware_update_info.status = 1 if update_available else 0
with expected_result:
await fw.update(progress_cb=cb_mock)
if not update_available:
return
# This is necessary to allow the eventloop to process the created tasks
await asyncio_sleep(0)
assert "Unhandled state code" in caplog.text
assert "Downloading firmware, progress: 10" in caplog.text
assert "Flashing firmware, sleeping" in caplog.text
assert "Update idle" in caplog.text
for state in update_states:
cb_mock.assert_any_await(state)
# sleep based on the upgrade_time
sleep.assert_any_call(upgrade_time)

View File

@@ -0,0 +1,29 @@
import pytest
from kasa.smart.modules import HumiditySensor
from ...device_fixtures import parametrize
humidity = parametrize(
"has humidity", component_filter="humidity", protocol_filter={"SMART.CHILD"}
)
@humidity
@pytest.mark.parametrize(
("feature", "type"),
[
("humidity", int),
("humidity_warning", bool),
],
)
async def test_humidity_features(dev, feature, type):
"""Test that features are registered and work as expected."""
humidity: HumiditySensor = dev.modules["HumiditySensor"]
prop = getattr(humidity, feature)
assert isinstance(prop, type)
feat = dev.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)

View File

@@ -0,0 +1,84 @@
from __future__ import annotations
from itertools import chain
import pytest
from pytest_mock import MockerFixture
from kasa import Device, Feature, Module
from kasa.smart.modules import LightEffect
from ...device_fixtures import parametrize
light_effect = parametrize(
"has light effect", component_filter="light_effect", protocol_filter={"SMART"}
)
@light_effect
async def test_light_effect(dev: Device, mocker: MockerFixture):
"""Test light effect."""
light_effect = dev.modules.get(Module.LightEffect)
assert isinstance(light_effect, LightEffect)
feature = dev.features["light_effect"]
assert feature.type == Feature.Type.Choice
call = mocker.spy(light_effect, "call")
assert feature.choices == light_effect.effect_list
assert feature.choices
for effect in chain(reversed(feature.choices), feature.choices):
await light_effect.set_effect(effect)
enable = effect != LightEffect.LIGHT_EFFECTS_OFF
params: dict[str, bool | str] = {"enable": enable}
if enable:
params["id"] = light_effect._scenes_names_to_id[effect]
call.assert_called_with("set_dynamic_light_effect_rule_enable", params)
await dev.update()
assert light_effect.effect == effect
assert feature.value == effect
with pytest.raises(ValueError, match="The effect foobar is not a built in effect"):
await light_effect.set_effect("foobar")
@light_effect
@pytest.mark.parametrize("effect_active", [True, False])
async def test_light_effect_brightness(
dev: Device, effect_active: bool, mocker: MockerFixture
):
"""Test that light module uses light_effect for brightness when active."""
light_module = dev.modules[Module.Light]
light_effect = dev.modules[Module.SmartLightEffect]
light_effect_set_brightness = mocker.spy(light_effect, "set_brightness")
mock_light_effect_call = mocker.patch.object(light_effect, "call")
brightness = dev.modules[Module.Brightness]
brightness_set_brightness = mocker.spy(brightness, "set_brightness")
mock_brightness_call = mocker.patch.object(brightness, "call")
mocker.patch.object(
type(light_effect),
"is_active",
new_callable=mocker.PropertyMock,
return_value=effect_active,
)
if effect_active: # Set the rule L1 active for testing
light_effect.data["current_rule_id"] = "L1"
await light_module.set_brightness(10)
if effect_active:
assert light_effect.is_active
assert light_effect.brightness == dev.brightness
light_effect_set_brightness.assert_called_with(10)
mock_light_effect_call.assert_called_with(
"edit_dynamic_light_effect_rule", mocker.ANY
)
else:
assert not light_effect.is_active
brightness_set_brightness.assert_called_with(10)
mock_brightness_call.assert_called_with("set_device_info", {"brightness": 10})

View File

@@ -0,0 +1,99 @@
from __future__ import annotations
from itertools import chain
import pytest
from pytest_mock import MockerFixture
from kasa import Device, Feature, Module
from kasa.smart.modules import LightEffect, LightStripEffect
from ...device_fixtures import parametrize
light_strip_effect = parametrize(
"has light strip effect",
component_filter="light_strip_lighting_effect",
protocol_filter={"SMART"},
)
@light_strip_effect
async def test_light_strip_effect(dev: Device, mocker: MockerFixture):
"""Test light strip effect."""
light_effect = dev.modules.get(Module.LightEffect)
assert isinstance(light_effect, LightStripEffect)
brightness = dev.modules[Module.Brightness]
feature = dev.features["light_effect"]
assert feature.type == Feature.Type.Choice
call = mocker.spy(light_effect, "call")
assert feature.choices == light_effect.effect_list
assert feature.choices
for effect in chain(reversed(feature.choices), feature.choices):
if effect == LightEffect.LIGHT_EFFECTS_OFF:
off_effect = (
light_effect.effect
if light_effect.effect in light_effect._effect_mapping
else "Aurora"
)
await light_effect.set_effect(effect)
if effect != LightEffect.LIGHT_EFFECTS_OFF:
params = {**light_effect._effect_mapping[effect]}
else:
params = {**light_effect._effect_mapping[off_effect]}
params["enable"] = 0
params["brightness"] = brightness.brightness # use the existing brightness
call.assert_called_with("set_lighting_effect", params)
await dev.update()
assert light_effect.effect == effect
assert feature.value == effect
with pytest.raises(ValueError, match="The effect foobar is not a built in effect"):
await light_effect.set_effect("foobar")
@light_strip_effect
@pytest.mark.parametrize("effect_active", [True, False])
async def test_light_effect_brightness(
dev: Device, effect_active: bool, mocker: MockerFixture
):
"""Test that light module uses light_effect for brightness when active."""
light_module = dev.modules[Module.Light]
light_effect = dev.modules[Module.SmartLightEffect]
light_effect_set_brightness = mocker.spy(light_effect, "set_brightness")
mock_light_effect_call = mocker.patch.object(light_effect, "call")
brightness = dev.modules[Module.Brightness]
brightness_set_brightness = mocker.spy(brightness, "set_brightness")
mock_brightness_call = mocker.patch.object(brightness, "call")
mocker.patch.object(
type(light_effect),
"is_active",
new_callable=mocker.PropertyMock,
return_value=effect_active,
)
await light_module.set_brightness(10)
if effect_active:
assert light_effect.is_active
assert light_effect.brightness == dev.brightness
light_effect_set_brightness.assert_called_with(10)
mock_light_effect_call.assert_called_with(
"set_lighting_effect", {"brightness": 10, "bAdjusted": True}
)
else:
assert not light_effect.is_active
brightness_set_brightness.assert_called_with(10)
mock_brightness_call.assert_called_with("set_device_info", {"brightness": 10})

View File

@@ -0,0 +1,81 @@
from pytest_mock import MockerFixture
from kasa import Feature, Module
from kasa.smart import SmartDevice
from ...device_fixtures import get_parent_and_child_modules, parametrize
from ...fixtureinfo import ComponentFilter
light_transition_v1 = parametrize(
"has light transition",
component_filter=ComponentFilter(
component_name="on_off_gradually", maximum_version=1
),
protocol_filter={"SMART"},
)
light_transition_gt_v1 = parametrize(
"has light transition",
component_filter=ComponentFilter(
component_name="on_off_gradually", minimum_version=2
),
protocol_filter={"SMART"},
)
@light_transition_v1
async def test_module_v1(dev: SmartDevice, mocker: MockerFixture):
"""Test light transition module."""
assert isinstance(dev, SmartDevice)
light_transition = next(get_parent_and_child_modules(dev, Module.LightTransition))
assert light_transition
assert "smooth_transitions" in light_transition._module_features
assert "smooth_transition_on" not in light_transition._module_features
assert "smooth_transition_off" not in light_transition._module_features
await light_transition.set_enabled(True)
await dev.update()
assert light_transition.enabled is True
await light_transition.set_enabled(False)
await dev.update()
assert light_transition.enabled is False
@light_transition_gt_v1
async def test_module_gt_v1(dev: SmartDevice, mocker: MockerFixture):
"""Test light transition module."""
assert isinstance(dev, SmartDevice)
light_transition = next(get_parent_and_child_modules(dev, Module.LightTransition))
assert light_transition
assert "smooth_transitions" not in light_transition._module_features
assert "smooth_transition_on" in light_transition._module_features
assert "smooth_transition_off" in light_transition._module_features
await light_transition.set_enabled(True)
await dev.update()
assert light_transition.enabled is True
await light_transition.set_enabled(False)
await dev.update()
assert light_transition.enabled is False
await light_transition.set_turn_on_transition(5)
await dev.update()
assert light_transition.turn_on_transition == 5
# enabled is true if either on or off is enabled
assert light_transition.enabled is True
await light_transition.set_turn_off_transition(10)
await dev.update()
assert light_transition.turn_off_transition == 10
assert light_transition.enabled is True
max_on = light_transition._module_features["smooth_transition_on"].maximum_value
assert max_on < Feature.DEFAULT_MAX
max_off = light_transition._module_features["smooth_transition_off"].maximum_value
assert max_off < Feature.DEFAULT_MAX
await light_transition.set_turn_on_transition(0)
await light_transition.set_turn_off_transition(0)
await dev.update()
assert light_transition.enabled is False

View File

@@ -0,0 +1,29 @@
import pytest
from kasa import Module, SmartDevice
from ...device_fixtures import parametrize
motion = parametrize(
"is motion sensor", model_filter="T100", protocol_filter={"SMART.CHILD"}
)
@motion
@pytest.mark.parametrize(
("feature", "type"),
[
("motion_detected", bool),
],
)
async def test_motion_features(dev: SmartDevice, feature, type):
"""Test that features are registered and work as expected."""
motion = dev.modules.get(Module.MotionSensor)
assert motion is not None
prop = getattr(motion, feature)
assert isinstance(prop, type)
feat = dev.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)

View File

@@ -0,0 +1,48 @@
import pytest
from kasa.smart.modules import TemperatureSensor
from ...device_fixtures import parametrize
temperature = parametrize(
"has temperature", component_filter="temperature", protocol_filter={"SMART.CHILD"}
)
temperature_warning = parametrize(
"has temperature warning",
component_filter="comfort_temperature",
protocol_filter={"SMART.CHILD"},
)
@temperature
@pytest.mark.parametrize(
("feature", "type"),
[
("temperature", float),
("temperature_unit", str),
],
)
async def test_temperature_features(dev, feature, type):
"""Test that features are registered and work as expected."""
temp_module: TemperatureSensor = dev.modules["TemperatureSensor"]
prop = getattr(temp_module, feature)
assert isinstance(prop, type)
feat = dev.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)
@temperature_warning
async def test_temperature_warning(dev):
"""Test that features are registered and work as expected."""
temp_module: TemperatureSensor = dev.modules["TemperatureSensor"]
assert hasattr(temp_module, "temperature_warning")
assert isinstance(temp_module.temperature_warning, bool)
feat = dev.features["temperature_warning"]
assert feat.value == temp_module.temperature_warning
assert isinstance(feat.value, bool)

View File

@@ -0,0 +1,155 @@
import logging
import re
import pytest
from kasa.smart.modules import TemperatureControl
from kasa.smart.modules.temperaturecontrol import ThermostatState
from ...device_fixtures import parametrize, thermostats_smart
temperature = parametrize(
"has temperature control",
component_filter="temperature_control",
protocol_filter={"SMART.CHILD"},
)
@thermostats_smart
@pytest.mark.parametrize(
("feature", "type"),
[
("target_temperature", float),
("temperature_offset", int),
],
)
async def test_temperature_control_features(dev, feature, type):
"""Test that features are registered and work as expected."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
prop = getattr(temp_module, feature)
assert isinstance(prop, type)
feat = dev.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)
await feat.set_value(10)
await dev.update()
assert feat.value == 10
@thermostats_smart
async def test_set_temperature_turns_heating_on(dev):
"""Test that set_temperature turns heating on."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
await temp_module.set_state(False)
await dev.update()
assert temp_module.state is False
assert temp_module.mode is ThermostatState.Off
await temp_module.set_target_temperature(10)
await dev.update()
assert temp_module.state is True
assert temp_module.mode is ThermostatState.Heating
assert temp_module.target_temperature == 10
@thermostats_smart
async def test_set_temperature_invalid_values(dev):
"""Test that out-of-bounds temperature values raise errors."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
with pytest.raises(
ValueError, match="Invalid target temperature -1, must be in range"
):
await temp_module.set_target_temperature(-1)
with pytest.raises(
ValueError, match="Invalid target temperature 100, must be in range"
):
await temp_module.set_target_temperature(100)
@thermostats_smart
async def test_temperature_offset(dev):
"""Test the temperature offset API."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
with pytest.raises(
ValueError, match=re.escape("Temperature offset must be [-10, 10]")
):
await temp_module.set_temperature_offset(100)
with pytest.raises(
ValueError, match=re.escape("Temperature offset must be [-10, 10]")
):
await temp_module.set_temperature_offset(-100)
await temp_module.set_temperature_offset(5)
await dev.update()
assert temp_module.temperature_offset == 5
@thermostats_smart
@pytest.mark.parametrize(
("mode", "states", "frost_protection"),
[
pytest.param(ThermostatState.Idle, [], False, id="idle has empty"),
pytest.param(
ThermostatState.Off,
["anything"],
True,
id="any state with frost_protection on means off",
),
pytest.param(
ThermostatState.Heating,
["heating"],
False,
id="heating is heating",
),
pytest.param(ThermostatState.Unknown, ["invalid"], False, id="unknown state"),
],
)
async def test_thermostat_mode(dev, mode, states, frost_protection):
"""Test different thermostat modes."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
temp_module.data["frost_protection_on"] = frost_protection
temp_module.data["trv_states"] = states
assert temp_module.state is not frost_protection
assert temp_module.mode is mode
@thermostats_smart
@pytest.mark.parametrize(
("mode", "states", "msg"),
[
pytest.param(
ThermostatState.Heating,
["heating", "something else"],
"Got multiple states",
id="multiple states",
),
pytest.param(
ThermostatState.Unknown, ["foobar"], "Got unknown state", id="unknown state"
),
],
)
async def test_thermostat_mode_warnings(dev, mode, states, msg, caplog):
"""Test thermostat modes that should log a warning."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
caplog.set_level(logging.WARNING)
temp_module.data["trv_states"] = states
assert temp_module.mode is mode
assert msg in caplog.text
@thermostats_smart
async def test_thermostat_heating_with_low_battery(dev):
"""Test that mode is reported correctly with extra states."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
temp_module.data["trv_states"] = ["low_battery", "heating"]
assert temp_module.mode is ThermostatState.Heating

View File

@@ -0,0 +1,46 @@
from datetime import datetime
from enum import Enum
import pytest
from kasa.smart.modules import WaterleakSensor
from ...device_fixtures import parametrize
waterleak = parametrize(
"has waterleak", component_filter="sensor_alarm", protocol_filter={"SMART.CHILD"}
)
@waterleak
@pytest.mark.parametrize(
("feature", "prop_name", "type"),
[
("water_alert", "alert", int),
# Can be converted to 'datetime | None' after py3.9 support is dropped
("water_alert_timestamp", "alert_timestamp", (datetime, type(None))),
("water_leak", "status", Enum),
],
)
async def test_waterleak_properties(dev, feature, prop_name, type):
"""Test that features are registered and work as expected."""
waterleak: WaterleakSensor = dev.modules["WaterleakSensor"]
prop = getattr(waterleak, prop_name)
assert isinstance(prop, type)
feat = dev.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)
@waterleak
async def test_waterleak_features(dev):
"""Test waterleak features."""
waterleak: WaterleakSensor = dev.modules["WaterleakSensor"]
assert "water_leak" in dev.features
assert dev.features["water_leak"].value == waterleak.status
assert "water_alert" in dev.features
assert dev.features["water_alert"].value == waterleak.alert