mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-10-15 20:08:06 +00:00
Merge remote-tracking branch 'upstream/master' into feat/device_update
This commit is contained in:
28
kasa/tests/smart/modules/test_contact.py
Normal file
28
kasa/tests/smart/modules/test_contact.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
|
||||
from kasa import Module, SmartDevice
|
||||
from kasa.tests.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)
|
@@ -1,8 +1,8 @@
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Module
|
||||
from kasa.smart import SmartDevice
|
||||
from kasa.smart.modules import FanModule
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"SMART"})
|
||||
@@ -11,10 +11,10 @@ fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"S
|
||||
@fan
|
||||
async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test fan speed feature."""
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
|
||||
level_feature = fan._module_features["fan_speed_level"]
|
||||
level_feature = dev.features["fan_speed_level"]
|
||||
assert (
|
||||
level_feature.minimum_value
|
||||
<= level_feature.value
|
||||
@@ -36,9 +36,9 @@ async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
|
||||
@fan
|
||||
async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test sleep mode feature."""
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
sleep_feature = fan._module_features["fan_sleep_mode"]
|
||||
sleep_feature = dev.features["fan_sleep_mode"]
|
||||
assert isinstance(sleep_feature.value, bool)
|
||||
|
||||
call = mocker.spy(fan, "call")
|
||||
@@ -52,29 +52,28 @@ async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
|
||||
|
||||
|
||||
@fan
|
||||
async def test_fan_interface(dev: SmartDevice, mocker: MockerFixture):
|
||||
async def test_fan_module(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test fan speed on device interface."""
|
||||
assert isinstance(dev, SmartDevice)
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
device = fan._device
|
||||
assert device.is_fan
|
||||
|
||||
await device.set_fan_speed_level(1)
|
||||
await fan.set_fan_speed_level(1)
|
||||
await dev.update()
|
||||
assert device.fan_speed_level == 1
|
||||
assert fan.fan_speed_level == 1
|
||||
assert device.is_on
|
||||
|
||||
await device.set_fan_speed_level(4)
|
||||
await fan.set_fan_speed_level(4)
|
||||
await dev.update()
|
||||
assert device.fan_speed_level == 4
|
||||
assert fan.fan_speed_level == 4
|
||||
|
||||
await device.set_fan_speed_level(0)
|
||||
await fan.set_fan_speed_level(0)
|
||||
await dev.update()
|
||||
assert not device.is_on
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await device.set_fan_speed_level(-1)
|
||||
await fan.set_fan_speed_level(-1)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await device.set_fan_speed_level(5)
|
||||
await fan.set_fan_speed_level(5)
|
||||
|
113
kasa/tests/smart/modules/test_firmware.py
Normal file
113
kasa/tests/smart/modules/test_firmware.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Module
|
||||
from kasa.smart import SmartDevice
|
||||
from kasa.smart.modules.firmware import DownloadState, Firmware
|
||||
from kasa.tests.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),
|
||||
("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
|
||||
if not isinstance(fw, Firmware): # TODO needed while common interface still TBD
|
||||
return
|
||||
if not dev.is_cloud_connected:
|
||||
pytest.skip("Device is not cloud connected, skipping test")
|
||||
|
||||
if fw.supported_version < required_version:
|
||||
pytest.skip("Feature %s requires newer version" % feature)
|
||||
|
||||
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
|
||||
if not isinstance(fw, Firmware): # TODO needed while common interface still TBD
|
||||
return
|
||||
|
||||
if dev.is_cloud_connected:
|
||||
assert isinstance(fw.update_available, bool)
|
||||
else:
|
||||
assert fw.update_available is None
|
||||
|
||||
|
||||
@firmware
|
||||
async def test_firmware_update(
|
||||
dev: SmartDevice, mocker: MockerFixture, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test updating firmware."""
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
fw = dev.modules.get(Module.Firmware)
|
||||
assert fw
|
||||
if not isinstance(fw, Firmware): # TODO needed while common interface still TBD
|
||||
return
|
||||
|
||||
upgrade_time = 5
|
||||
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()
|
||||
|
||||
await fw.update(progress_cb=cb_mock)
|
||||
|
||||
# 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)
|
@@ -23,6 +23,6 @@ async def test_humidity_features(dev, feature, type):
|
||||
prop = getattr(humidity, feature)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = humidity._module_features[feature]
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
@@ -1,13 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import chain
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Device, Feature
|
||||
from kasa.smart.modules import LightEffectModule
|
||||
from kasa import Device, Feature, Module
|
||||
from kasa.smart.modules import LightEffect
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
light_effect = parametrize(
|
||||
@@ -18,10 +17,10 @@ light_effect = parametrize(
|
||||
@light_effect
|
||||
async def test_light_effect(dev: Device, mocker: MockerFixture):
|
||||
"""Test light effect."""
|
||||
light_effect = cast(LightEffectModule, dev.modules.get("LightEffectModule"))
|
||||
assert light_effect
|
||||
light_effect = dev.modules.get(Module.LightEffect)
|
||||
assert isinstance(light_effect, LightEffect)
|
||||
|
||||
feature = light_effect._module_features["light_effect"]
|
||||
feature = dev.features["light_effect"]
|
||||
assert feature.type == Feature.Type.Choice
|
||||
|
||||
call = mocker.spy(light_effect, "call")
|
||||
@@ -29,7 +28,7 @@ async def test_light_effect(dev: Device, mocker: MockerFixture):
|
||||
assert feature.choices
|
||||
for effect in chain(reversed(feature.choices), feature.choices):
|
||||
await light_effect.set_effect(effect)
|
||||
enable = effect != LightEffectModule.LIGHT_EFFECTS_OFF
|
||||
enable = effect != LightEffect.LIGHT_EFFECTS_OFF
|
||||
params: dict[str, bool | str] = {"enable": enable}
|
||||
if enable:
|
||||
params["id"] = light_effect._scenes_names_to_id[effect]
|
||||
|
@@ -29,7 +29,7 @@ async def test_temperature_features(dev, feature, type):
|
||||
prop = getattr(temp_module, feature)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = temp_module._module_features[feature]
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
||||
@@ -42,6 +42,6 @@ async def test_temperature_warning(dev):
|
||||
assert hasattr(temp_module, "temperature_warning")
|
||||
assert isinstance(temp_module.temperature_warning, bool)
|
||||
|
||||
feat = temp_module._module_features["temperature_warning"]
|
||||
feat = dev.features["temperature_warning"]
|
||||
assert feat.value == temp_module.temperature_warning
|
||||
assert isinstance(feat.value, bool)
|
||||
|
@@ -28,7 +28,7 @@ async def test_temperature_control_features(dev, feature, type):
|
||||
prop = getattr(temp_module, feature)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = temp_module._module_features[feature]
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
||||
|
@@ -25,7 +25,7 @@ async def test_waterleak_properties(dev, feature, prop_name, type):
|
||||
prop = getattr(waterleak, prop_name)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = waterleak._module_features[feature]
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
||||
|
Reference in New Issue
Block a user