mirror of
https://github.com/python-kasa/python-kasa.git
synced 2026-07-08 14:52:03 +00:00
Some checks are pending
CI / Perform Lint Checks (3.14) (push) Waiting to run
CI / Python 3.11 on macos-latest (push) Blocked by required conditions
CI / Python 3.12 on macos-latest (push) Blocked by required conditions
CI / Python 3.13 on macos-latest (push) Blocked by required conditions
CI / Python 3.14 on macos-latest (push) Blocked by required conditions
CI / Python 3.11 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.12 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.13 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.14 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.11 on windows-latest (push) Blocked by required conditions
CI / Python 3.12 on windows-latest (push) Blocked by required conditions
CI / Python 3.13 on windows-latest (push) Blocked by required conditions
CI / Python 3.14 on windows-latest (push) Blocked by required conditions
CodeQL Checks / Analyze (python) (push) Waiting to run
Add type annotations across all 'smart' tests, enabling mypy to check test function bodies, catching type errors that were previously hidden.
180 lines
5.3 KiB
Python
180 lines
5.3 KiB
Python
import logging
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from kasa import Module
|
|
from kasa.smart import SmartDevice
|
|
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: SmartDevice, feature: str, type: type
|
|
) -> None:
|
|
"""Test that features are registered and work as expected."""
|
|
temp_module = dev.modules[Module.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: SmartDevice) -> None:
|
|
"""Test that set_temperature turns heating on."""
|
|
temp_module = dev.modules[Module.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: SmartDevice) -> None:
|
|
"""Test that out-of-bounds temperature values raise errors."""
|
|
temp_module = dev.modules[Module.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: SmartDevice) -> None:
|
|
"""Test the temperature offset API."""
|
|
temp_module = dev.modules[Module.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: SmartDevice, mode: ThermostatState, states: list[str], frost_protection: bool
|
|
) -> None:
|
|
"""Test different thermostat modes."""
|
|
temp_module = dev.modules[Module.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"
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.xdist_group(name="caplog")
|
|
async def test_thermostat_mode_warnings(
|
|
dev: SmartDevice,
|
|
mode: ThermostatState,
|
|
states: list[str],
|
|
msg: str,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Test thermostat modes that should log a warning."""
|
|
temp_module = dev.modules[Module.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: SmartDevice) -> None:
|
|
"""Test that mode is reported correctly with extra states."""
|
|
temp_module = dev.modules[Module.TemperatureControl]
|
|
temp_module.data["trv_states"] = ["low_battery", "heating"]
|
|
assert temp_module.mode is ThermostatState.Heating
|
|
|
|
|
|
@thermostats_smart
|
|
async def test_thermostat_idle_with_low_battery(
|
|
dev: SmartDevice, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Test that mode is reported correctly with extra states."""
|
|
temp_module = dev.modules[Module.TemperatureControl]
|
|
temp_module.data["trv_states"] = ["low_battery"]
|
|
with caplog.at_level(logging.WARNING):
|
|
assert temp_module.mode is ThermostatState.Idle
|
|
assert not caplog.records
|