Fix warnings in our test suite (#1246)

Co-authored-by: Steven B <51370195+sdb9696@users.noreply.github.com>
This commit is contained in:
Teemu R. 2024-11-13 17:10:06 +01:00 committed by GitHub
parent 157ad8e807
commit a82ee56a27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 197 additions and 155 deletions

View File

@ -484,11 +484,11 @@ class Device(ABC):
_deprecated_device_type_attributes = { _deprecated_device_type_attributes = {
# is_type # is_type
"is_bulb": (Module.Light, DeviceType.Bulb), "is_bulb": (None, DeviceType.Bulb),
"is_dimmer": (Module.Light, DeviceType.Dimmer), "is_dimmer": (None, DeviceType.Dimmer),
"is_light_strip": (Module.LightEffect, DeviceType.LightStrip), "is_light_strip": (None, DeviceType.LightStrip),
"is_plug": (Module.Led, DeviceType.Plug), "is_plug": (None, DeviceType.Plug),
"is_wallswitch": (Module.Led, DeviceType.WallSwitch), "is_wallswitch": (None, DeviceType.WallSwitch),
"is_strip": (None, DeviceType.Strip), "is_strip": (None, DeviceType.Strip),
"is_strip_socket": (None, DeviceType.StripSocket), "is_strip_socket": (None, DeviceType.StripSocket),
} }
@ -503,7 +503,9 @@ class Device(ABC):
return None return None
for attr in attrs: for attr in attrs:
if hasattr(check, attr): # Use dir() as opposed to hasattr() to avoid raising exceptions
# from properties
if attr in dir(check):
return attr return attr
return None return None
@ -552,10 +554,7 @@ class Device(ABC):
def __getattr__(self, name: str) -> Any: def __getattr__(self, name: str) -> Any:
# is_device_type # is_device_type
if dep_device_type_attr := self._deprecated_device_type_attributes.get(name): if dep_device_type_attr := self._deprecated_device_type_attributes.get(name):
module = dep_device_type_attr[0] msg = f"{name} is deprecated, use device_type property instead"
msg = f"{name} is deprecated"
if module:
msg += f", use: {module} in device.modules instead"
warn(msg, DeprecationWarning, stacklevel=2) warn(msg, DeprecationWarning, stacklevel=2)
return self.device_type == dep_device_type_attr[1] return self.device_type == dep_device_type_attr[1]
# Other deprecated attributes # Other deprecated attributes

View File

@ -33,6 +33,7 @@ class FakeSmartTransport(BaseTransport):
warn_fixture_missing_methods=True, warn_fixture_missing_methods=True,
fix_incomplete_fixture_lists=True, fix_incomplete_fixture_lists=True,
is_child=False, is_child=False,
get_child_fixtures=True,
): ):
super().__init__( super().__init__(
config=DeviceConfig( config=DeviceConfig(
@ -48,6 +49,7 @@ class FakeSmartTransport(BaseTransport):
# child are then still reflected on the parent's lis of child device in # child are then still reflected on the parent's lis of child device in
if not is_child: if not is_child:
self.info = copy.deepcopy(info) self.info = copy.deepcopy(info)
if get_child_fixtures:
self.child_protocols = self._get_child_protocols( self.child_protocols = self._get_child_protocols(
self.info, self.fixture_name, "get_child_device_list" self.info, self.fixture_name, "get_child_device_list"
) )
@ -220,10 +222,7 @@ class FakeSmartTransport(BaseTransport):
"""Handle control_child command.""" """Handle control_child command."""
device_id = params.get("device_id") device_id = params.get("device_id")
if device_id not in self.child_protocols: if device_id not in self.child_protocols:
warn( # no need to warn as the warning was raised during protocol init
f"Could not find child fixture {device_id} in {self.fixture_name}",
stacklevel=2,
)
return self._handle_control_child_missing(params) return self._handle_control_child_missing(params)
child_protocol: SmartProtocol = self.child_protocols[device_id] child_protocol: SmartProtocol = self.child_protocols[device_id]

View File

@ -1,6 +1,6 @@
import pytest import pytest
from kasa import Module, SmartDevice from kasa import Device, Module
from ...device_fixtures import parametrize from ...device_fixtures import parametrize
@ -16,7 +16,7 @@ contact = parametrize(
("is_open", bool), ("is_open", bool),
], ],
) )
async def test_contact_features(dev: SmartDevice, feature, type): async def test_contact_features(dev: Device, feature, type):
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
contact = dev.modules.get(Module.ContactSensor) contact = dev.modules.get(Module.ContactSensor)
assert contact is not None assert contact is not None

View File

@ -71,7 +71,7 @@ async def test_light_effect_brightness(
if effect_active: if effect_active:
assert light_effect.is_active assert light_effect.is_active
assert light_effect.brightness == dev.brightness assert light_effect.brightness == light_module.brightness
light_effect_set_brightness.assert_called_with(10) light_effect_set_brightness.assert_called_with(10)
mock_light_effect_call.assert_called_with( mock_light_effect_call.assert_called_with(

View File

@ -86,7 +86,7 @@ async def test_light_effect_brightness(
if effect_active: if effect_active:
assert light_effect.is_active assert light_effect.is_active
assert light_effect.brightness == dev.brightness assert light_effect.brightness == light_module.brightness
light_effect_set_brightness.assert_called_with(10) light_effect_set_brightness.assert_called_with(10)
mock_light_effect_call.assert_called_with( mock_light_effect_call.assert_called_with(

View File

@ -1,6 +1,6 @@
import pytest import pytest
from kasa import Module, SmartDevice from kasa import Device, Module
from ...device_fixtures import parametrize from ...device_fixtures import parametrize
@ -16,7 +16,7 @@ motion = parametrize(
("motion_detected", bool), ("motion_detected", bool),
], ],
) )
async def test_motion_features(dev: SmartDevice, feature, type): async def test_motion_features(dev: Device, feature, type):
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
motion = dev.modules.get(Module.MotionSensor) motion = dev.modules.get(Module.MotionSensor)
assert motion is not None assert motion is not None

View File

@ -13,6 +13,7 @@ from voluptuous import (
from kasa import Device, DeviceType, IotLightPreset, KasaException, LightState, Module from kasa import Device, DeviceType, IotLightPreset, KasaException, LightState, Module
from kasa.iot import IotBulb, IotDimmer from kasa.iot import IotBulb, IotDimmer
from kasa.iot.modules import LightPreset as IotLightPresetModule
from .conftest import ( from .conftest import (
bulb, bulb,
@ -39,11 +40,6 @@ async def test_bulb_sysinfo(dev: Device):
assert dev.model is not None assert dev.model is not None
# TODO: remove special handling for lightstrip
if not dev.is_light_strip:
assert dev.device_type == DeviceType.Bulb
assert dev.is_bulb
@bulb @bulb
async def test_state_attributes(dev: Device): async def test_state_attributes(dev: Device):
@ -88,7 +84,9 @@ async def test_hsv(dev: Device, turn_on):
@color_bulb_iot @color_bulb_iot
async def test_set_hsv_transition(dev: IotBulb, mocker): async def test_set_hsv_transition(dev: IotBulb, mocker):
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state") set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
await dev.set_hsv(10, 10, 100, transition=1000) light = dev.modules.get(Module.Light)
assert light
await light.set_hsv(10, 10, 100, transition=1000)
set_light_state.assert_called_with( set_light_state.assert_called_with(
{"hue": 10, "saturation": 10, "brightness": 100, "color_temp": 0}, {"hue": 10, "saturation": 10, "brightness": 100, "color_temp": 0},
@ -226,7 +224,9 @@ async def test_try_set_colortemp(dev: Device, turn_on):
@variable_temp_iot @variable_temp_iot
async def test_set_color_temp_transition(dev: IotBulb, mocker): async def test_set_color_temp_transition(dev: IotBulb, mocker):
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state") set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
await dev.set_color_temp(2700, transition=100) light = dev.modules.get(Module.Light)
assert light
await light.set_color_temp(2700, transition=100)
set_light_state.assert_called_with({"color_temp": 2700}, transition=100) set_light_state.assert_called_with({"color_temp": 2700}, transition=100)
@ -234,8 +234,9 @@ async def test_set_color_temp_transition(dev: IotBulb, mocker):
@variable_temp_iot @variable_temp_iot
async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog): async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb") monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
light = dev.modules.get(Module.Light)
assert dev.valid_temperature_range == (2700, 5000) assert light
assert light.valid_temperature_range == (2700, 5000)
assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text
@ -278,19 +279,21 @@ async def test_non_variable_temp(dev: Device):
@turn_on @turn_on
async def test_dimmable_brightness(dev: IotBulb, turn_on): async def test_dimmable_brightness(dev: IotBulb, turn_on):
assert isinstance(dev, (IotBulb, IotDimmer)) assert isinstance(dev, (IotBulb, IotDimmer))
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
assert dev._is_dimmable assert dev._is_dimmable
await dev.set_brightness(50) await light.set_brightness(50)
await dev.update() await dev.update()
assert dev.brightness == 50 assert light.brightness == 50
await dev.set_brightness(10) await light.set_brightness(10)
await dev.update() await dev.update()
assert dev.brightness == 10 assert light.brightness == 10
with pytest.raises(TypeError, match="Brightness must be an integer"): with pytest.raises(TypeError, match="Brightness must be an integer"):
await dev.set_brightness("foo") # type: ignore[arg-type] await light.set_brightness("foo") # type: ignore[arg-type]
@bulb_iot @bulb_iot
@ -308,7 +311,9 @@ async def test_turn_on_transition(dev: IotBulb, mocker):
@bulb_iot @bulb_iot
async def test_dimmable_brightness_transition(dev: IotBulb, mocker): async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state") set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
await dev.set_brightness(10, transition=1000) light = dev.modules.get(Module.Light)
assert light
await light.set_brightness(10, transition=1000)
set_light_state.assert_called_with({"brightness": 10, "on_off": 1}, transition=1000) set_light_state.assert_called_with({"brightness": 10, "on_off": 1}, transition=1000)
@ -316,28 +321,30 @@ async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
@dimmable_iot @dimmable_iot
async def test_invalid_brightness(dev: IotBulb): async def test_invalid_brightness(dev: IotBulb):
assert dev._is_dimmable assert dev._is_dimmable
light = dev.modules.get(Module.Light)
assert light
with pytest.raises( with pytest.raises(
ValueError, ValueError,
match=re.escape("Invalid brightness value: 110 (valid range: 0-100%)"), match=re.escape("Invalid brightness value: 110 (valid range: 0-100%)"),
): ):
await dev.set_brightness(110) await light.set_brightness(110)
with pytest.raises( with pytest.raises(
ValueError, ValueError,
match=re.escape("Invalid brightness value: -100 (valid range: 0-100%)"), match=re.escape("Invalid brightness value: -100 (valid range: 0-100%)"),
): ):
await dev.set_brightness(-100) await light.set_brightness(-100)
@non_dimmable_iot @non_dimmable_iot
async def test_non_dimmable(dev: IotBulb): async def test_non_dimmable(dev: IotBulb):
assert not dev._is_dimmable assert not dev._is_dimmable
light = dev.modules.get(Module.Light)
assert light
with pytest.raises(KasaException): with pytest.raises(KasaException):
assert dev.brightness == 0 assert light.brightness == 0
with pytest.raises(KasaException): with pytest.raises(KasaException):
await dev.set_brightness(100) await light.set_brightness(100)
@bulb_iot @bulb_iot
@ -357,7 +364,10 @@ async def test_ignore_default_not_set_without_color_mode_change_turn_on(
@bulb_iot @bulb_iot
async def test_list_presets(dev: IotBulb): async def test_list_presets(dev: IotBulb):
presets = dev.presets light_preset = dev.modules.get(Module.LightPreset)
assert light_preset
assert isinstance(light_preset, IotLightPresetModule)
presets = light_preset._deprecated_presets
# Light strip devices may list some light effects along with normal presets but these # Light strip devices may list some light effects along with normal presets but these
# are handled by the LightEffect module so exclude preferred states with id # are handled by the LightEffect module so exclude preferred states with id
raw_presets = [ raw_presets = [
@ -376,9 +386,13 @@ async def test_list_presets(dev: IotBulb):
@bulb_iot @bulb_iot
async def test_modify_preset(dev: IotBulb, mocker): async def test_modify_preset(dev: IotBulb, mocker):
"""Verify that modifying preset calls the and exceptions are raised properly.""" """Verify that modifying preset calls the and exceptions are raised properly."""
if not dev.presets: if (
not (light_preset := dev.modules.get(Module.LightPreset))
or not light_preset._deprecated_presets
):
pytest.skip("Some strips do not support presets") pytest.skip("Some strips do not support presets")
assert isinstance(light_preset, IotLightPresetModule)
data: dict[str, int | None] = { data: dict[str, int | None] = {
"index": 0, "index": 0,
"brightness": 10, "brightness": 10,
@ -394,12 +408,12 @@ async def test_modify_preset(dev: IotBulb, mocker):
assert preset.saturation == 0 assert preset.saturation == 0
assert preset.color_temp == 0 assert preset.color_temp == 0
await dev.save_preset(preset) await light_preset._deprecated_save_preset(preset)
await dev.update() await dev.update()
assert dev.presets[0].brightness == 10 assert light_preset._deprecated_presets[0].brightness == 10
with pytest.raises(KasaException): with pytest.raises(KasaException):
await dev.save_preset( await light_preset._deprecated_save_preset(
IotLightPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0) # type: ignore[call-arg] IotLightPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0) # type: ignore[call-arg]
) )
@ -420,11 +434,14 @@ async def test_modify_preset(dev: IotBulb, mocker):
) )
async def test_modify_preset_payloads(dev: IotBulb, preset, payload, mocker): async def test_modify_preset_payloads(dev: IotBulb, preset, payload, mocker):
"""Test that modify preset payloads ignore none values.""" """Test that modify preset payloads ignore none values."""
if not dev.presets: if (
not (light_preset := dev.modules.get(Module.LightPreset))
or not light_preset._deprecated_presets
):
pytest.skip("Some strips do not support presets") pytest.skip("Some strips do not support presets")
query_helper = mocker.patch("kasa.iot.IotBulb._query_helper") query_helper = mocker.patch("kasa.iot.IotBulb._query_helper")
await dev.save_preset(preset) await light_preset._deprecated_save_preset(preset)
query_helper.assert_called_with(dev.LIGHT_SERVICE, "set_preferred_state", payload) query_helper.assert_called_with(dev.LIGHT_SERVICE, "set_preferred_state", payload)
@ -476,6 +493,4 @@ SYSINFO_SCHEMA_BULB = SYSINFO_SCHEMA.extend(
@bulb @bulb
def test_device_type_bulb(dev: Device): def test_device_type_bulb(dev: Device):
if dev.is_light_strip: assert dev.device_type in {DeviceType.Bulb, DeviceType.LightStrip}
pytest.skip("bulb has also lightstrips to test the api")
assert dev.device_type == DeviceType.Bulb

View File

@ -6,7 +6,7 @@ import importlib
import inspect import inspect
import pkgutil import pkgutil
import sys import sys
from contextlib import AbstractContextManager from contextlib import AbstractContextManager, nullcontext
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import pytest import pytest
@ -170,15 +170,22 @@ async def _test_attribute(
dev: Device, attribute_name, is_expected, module_name, *args, will_raise=False dev: Device, attribute_name, is_expected, module_name, *args, will_raise=False
): ):
if is_expected and will_raise: if is_expected and will_raise:
ctx: AbstractContextManager = pytest.raises(will_raise) ctx: AbstractContextManager | nullcontext = pytest.raises(will_raise)
dep_context: pytest.WarningsRecorder | nullcontext = pytest.deprecated_call(
match=(f"{attribute_name} is deprecated, use:")
)
elif is_expected: elif is_expected:
ctx = pytest.deprecated_call(match=(f"{attribute_name} is deprecated, use:")) ctx = nullcontext()
dep_context = pytest.deprecated_call(
match=(f"{attribute_name} is deprecated, use:")
)
else: else:
ctx = pytest.raises( ctx = pytest.raises(
AttributeError, match=f"Device has no attribute '{attribute_name}'" AttributeError, match=f"Device has no attribute '{attribute_name}'"
) )
dep_context = nullcontext()
with ctx: with dep_context, ctx:
if args: if args:
await getattr(dev, attribute_name)(*args) await getattr(dev, attribute_name)(*args)
else: else:
@ -267,16 +274,19 @@ async def test_deprecated_light_preset_attributes(dev: Device):
await _test_attribute(dev, "presets", bool(preset), "LightPreset", will_raise=exc) await _test_attribute(dev, "presets", bool(preset), "LightPreset", will_raise=exc)
exc = None exc = None
is_expected = bool(preset)
# deprecated save_preset not implemented for smart devices as it's unlikely anyone # deprecated save_preset not implemented for smart devices as it's unlikely anyone
# has an existing reliance on this for the newer devices. # has an existing reliance on this for the newer devices.
if not preset or isinstance(dev, SmartDevice): if isinstance(dev, SmartDevice):
exc = AttributeError is_expected = False
elif len(preset.preset_states_list) == 0:
if preset and len(preset.preset_states_list) == 0:
exc = KasaException exc = KasaException
await _test_attribute( await _test_attribute(
dev, dev,
"save_preset", "save_preset",
bool(preset), is_expected,
"LightPreset", "LightPreset",
IotLightPreset(index=0, hue=100, brightness=100, saturation=0, color_temp=0), # type: ignore[call-arg] IotLightPreset(index=0, hue=100, brightness=100, saturation=0, color_temp=0), # type: ignore[call-arg]
will_raise=exc, will_raise=exc,

View File

@ -1,6 +1,6 @@
import pytest import pytest
from kasa import DeviceType from kasa import DeviceType, Module
from kasa.iot import IotDimmer from kasa.iot import IotDimmer
from .conftest import dimmer_iot, handle_turn_on, turn_on from .conftest import dimmer_iot, handle_turn_on, turn_on
@ -8,28 +8,32 @@ from .conftest import dimmer_iot, handle_turn_on, turn_on
@dimmer_iot @dimmer_iot
async def test_set_brightness(dev): async def test_set_brightness(dev):
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, False) await handle_turn_on(dev, False)
await dev.update() await dev.update()
assert dev.is_on is False assert dev.is_on is False
await dev.set_brightness(99) await light.set_brightness(99)
await dev.update() await dev.update()
assert dev.brightness == 99 assert light.brightness == 99
assert dev.is_on is True assert dev.is_on is True
await dev.set_brightness(0) await light.set_brightness(0)
await dev.update() await dev.update()
assert dev.brightness == 99 assert light.brightness == 99
assert dev.is_on is False assert dev.is_on is False
@dimmer_iot @dimmer_iot
@turn_on @turn_on
async def test_set_brightness_transition(dev, turn_on, mocker): async def test_set_brightness_transition(dev, turn_on, mocker):
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
query_helper = mocker.spy(IotDimmer, "_query_helper") query_helper = mocker.spy(IotDimmer, "_query_helper")
await dev.set_brightness(99, transition=1000) await light.set_brightness(99, transition=1000)
query_helper.assert_called_with( query_helper.assert_called_with(
mocker.ANY, mocker.ANY,
"smartlife.iot.dimmer", "smartlife.iot.dimmer",
@ -37,39 +41,45 @@ async def test_set_brightness_transition(dev, turn_on, mocker):
{"brightness": 99, "duration": 1000}, {"brightness": 99, "duration": 1000},
) )
await dev.update() await dev.update()
assert dev.brightness == 99 assert light.brightness == 99
assert dev.is_on assert dev.is_on
await dev.set_brightness(0, transition=1000) await light.set_brightness(0, transition=1000)
await dev.update() await dev.update()
assert dev.is_on is False assert dev.is_on is False
@dimmer_iot @dimmer_iot
async def test_set_brightness_invalid(dev): async def test_set_brightness_invalid(dev):
light = dev.modules.get(Module.Light)
assert light
for invalid_brightness in [-1, 101]: for invalid_brightness in [-1, 101]:
with pytest.raises(ValueError, match="Invalid brightness"): with pytest.raises(ValueError, match="Invalid brightness"):
await dev.set_brightness(invalid_brightness) await light.set_brightness(invalid_brightness)
for invalid_type in [0.5, "foo"]: for invalid_type in [0.5, "foo"]:
with pytest.raises(TypeError, match="Brightness must be an integer"): with pytest.raises(TypeError, match="Brightness must be an integer"):
await dev.set_brightness(invalid_type) await light.set_brightness(invalid_type)
@dimmer_iot @dimmer_iot
async def test_set_brightness_invalid_transition(dev): async def test_set_brightness_invalid_transition(dev):
light = dev.modules.get(Module.Light)
assert light
for invalid_transition in [-1]: for invalid_transition in [-1]:
with pytest.raises(ValueError, match="Transition value .+? is not valid."): with pytest.raises(ValueError, match="Transition value .+? is not valid."):
await dev.set_brightness(1, transition=invalid_transition) await light.set_brightness(1, transition=invalid_transition)
for invalid_type in [0.5, "foo"]: for invalid_type in [0.5, "foo"]:
with pytest.raises(TypeError, match="Transition must be integer"): with pytest.raises(TypeError, match="Transition must be integer"):
await dev.set_brightness(1, transition=invalid_type) await light.set_brightness(1, transition=invalid_type)
@dimmer_iot @dimmer_iot
async def test_turn_on_transition(dev, mocker): async def test_turn_on_transition(dev, mocker):
light = dev.modules.get(Module.Light)
assert light
query_helper = mocker.spy(IotDimmer, "_query_helper") query_helper = mocker.spy(IotDimmer, "_query_helper")
original_brightness = dev.brightness original_brightness = light.brightness
await dev.turn_on(transition=1000) await dev.turn_on(transition=1000)
query_helper.assert_called_with( query_helper.assert_called_with(
@ -80,20 +90,22 @@ async def test_turn_on_transition(dev, mocker):
) )
await dev.update() await dev.update()
assert dev.is_on assert dev.is_on
assert dev.brightness == original_brightness assert light.brightness == original_brightness
@dimmer_iot @dimmer_iot
async def test_turn_off_transition(dev, mocker): async def test_turn_off_transition(dev, mocker):
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, True) await handle_turn_on(dev, True)
query_helper = mocker.spy(IotDimmer, "_query_helper") query_helper = mocker.spy(IotDimmer, "_query_helper")
original_brightness = dev.brightness original_brightness = light.brightness
await dev.turn_off(transition=1000) await dev.turn_off(transition=1000)
await dev.update() await dev.update()
assert dev.is_off assert dev.is_off
assert dev.brightness == original_brightness assert light.brightness == original_brightness
query_helper.assert_called_with( query_helper.assert_called_with(
mocker.ANY, mocker.ANY,
"smartlife.iot.dimmer", "smartlife.iot.dimmer",
@ -105,6 +117,8 @@ async def test_turn_off_transition(dev, mocker):
@dimmer_iot @dimmer_iot
@turn_on @turn_on
async def test_set_dimmer_transition(dev, turn_on, mocker): async def test_set_dimmer_transition(dev, turn_on, mocker):
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
query_helper = mocker.spy(IotDimmer, "_query_helper") query_helper = mocker.spy(IotDimmer, "_query_helper")
@ -117,21 +131,23 @@ async def test_set_dimmer_transition(dev, turn_on, mocker):
) )
await dev.update() await dev.update()
assert dev.is_on assert dev.is_on
assert dev.brightness == 99 assert light.brightness == 99
@dimmer_iot @dimmer_iot
@turn_on @turn_on
async def test_set_dimmer_transition_to_off(dev, turn_on, mocker): async def test_set_dimmer_transition_to_off(dev, turn_on, mocker):
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
original_brightness = dev.brightness original_brightness = light.brightness
query_helper = mocker.spy(IotDimmer, "_query_helper") query_helper = mocker.spy(IotDimmer, "_query_helper")
await dev.set_dimmer_transition(0, 1000) await dev.set_dimmer_transition(0, 1000)
await dev.update() await dev.update()
assert dev.is_off assert dev.is_off
assert dev.brightness == original_brightness assert light.brightness == original_brightness
query_helper.assert_called_with( query_helper.assert_called_with(
mocker.ANY, mocker.ANY,
"smartlife.iot.dimmer", "smartlife.iot.dimmer",

View File

@ -81,14 +81,14 @@ UNSUPPORTED = {
@wallswitch_iot @wallswitch_iot
async def test_type_detection_switch(dev: Device): async def test_type_detection_switch(dev: Device):
d = Discover._get_device_class(dev._last_update)("localhost") d = Discover._get_device_class(dev._last_update)("localhost")
with pytest.deprecated_call(match="use device_type property instead"):
assert d.is_wallswitch assert d.is_wallswitch
assert d.device_type == DeviceType.WallSwitch assert d.device_type is DeviceType.WallSwitch
@plug_iot @plug_iot
async def test_type_detection_plug(dev: Device): async def test_type_detection_plug(dev: Device):
d = Discover._get_device_class(dev._last_update)("localhost") d = Discover._get_device_class(dev._last_update)("localhost")
assert d.is_plug
assert d.device_type == DeviceType.Plug assert d.device_type == DeviceType.Plug
@ -96,29 +96,26 @@ async def test_type_detection_plug(dev: Device):
async def test_type_detection_bulb(dev: Device): async def test_type_detection_bulb(dev: Device):
d = Discover._get_device_class(dev._last_update)("localhost") d = Discover._get_device_class(dev._last_update)("localhost")
# TODO: light_strip is a special case for now to force bulb tests on it # TODO: light_strip is a special case for now to force bulb tests on it
if not d.is_light_strip:
assert d.is_bulb if d.device_type is not DeviceType.LightStrip:
assert d.device_type == DeviceType.Bulb assert d.device_type == DeviceType.Bulb
@strip_iot @strip_iot
async def test_type_detection_strip(dev: Device): async def test_type_detection_strip(dev: Device):
d = Discover._get_device_class(dev._last_update)("localhost") d = Discover._get_device_class(dev._last_update)("localhost")
assert d.is_strip
assert d.device_type == DeviceType.Strip assert d.device_type == DeviceType.Strip
@dimmer_iot @dimmer_iot
async def test_type_detection_dimmer(dev: Device): async def test_type_detection_dimmer(dev: Device):
d = Discover._get_device_class(dev._last_update)("localhost") d = Discover._get_device_class(dev._last_update)("localhost")
assert d.is_dimmer
assert d.device_type == DeviceType.Dimmer assert d.device_type == DeviceType.Dimmer
@lightstrip_iot @lightstrip_iot
async def test_type_detection_lightstrip(dev: Device): async def test_type_detection_lightstrip(dev: Device):
d = Discover._get_device_class(dev._last_update)("localhost") d = Discover._get_device_class(dev._last_update)("localhost")
assert d.is_light_strip
assert d.device_type == DeviceType.LightStrip assert d.device_type == DeviceType.LightStrip

View File

@ -10,7 +10,7 @@ from voluptuous import (
Schema, Schema,
) )
from kasa import Device, EmeterStatus, Module from kasa import Device, DeviceType, EmeterStatus, Module
from kasa.interfaces.energy import Energy from kasa.interfaces.energy import Energy
from kasa.iot import IotDevice, IotStrip from kasa.iot import IotDevice, IotStrip
from kasa.iot.modules.emeter import Emeter from kasa.iot.modules.emeter import Emeter
@ -61,20 +61,20 @@ async def test_get_emeter_realtime(dev):
if not await mod._check_supported(): if not await mod._check_supported():
pytest.skip(f"Energy module not supported for {dev}.") pytest.skip(f"Energy module not supported for {dev}.")
assert dev.has_emeter emeter = dev.modules[Module.Energy]
current_emeter = await dev.get_emeter_realtime() current_emeter = await emeter.get_status()
CURRENT_CONSUMPTION_SCHEMA(current_emeter) CURRENT_CONSUMPTION_SCHEMA(current_emeter)
@has_emeter_iot @has_emeter_iot
@pytest.mark.requires_dummy() @pytest.mark.requires_dummy()
async def test_get_emeter_daily(dev): async def test_get_emeter_daily(dev):
assert dev.has_emeter emeter = dev.modules[Module.Energy]
assert await dev.get_emeter_daily(year=1900, month=1) == {} assert await emeter.get_daily_stats(year=1900, month=1) == {}
d = await dev.get_emeter_daily() d = await emeter.get_daily_stats()
assert len(d) > 0 assert len(d) > 0
k, v = d.popitem() k, v = d.popitem()
@ -82,7 +82,7 @@ async def test_get_emeter_daily(dev):
assert isinstance(v, float) assert isinstance(v, float)
# Test kwh (energy, energy_wh) # Test kwh (energy, energy_wh)
d = await dev.get_emeter_daily(kwh=False) d = await emeter.get_daily_stats(kwh=False)
k2, v2 = d.popitem() k2, v2 = d.popitem()
assert v * 1000 == v2 assert v * 1000 == v2
@ -90,11 +90,11 @@ async def test_get_emeter_daily(dev):
@has_emeter_iot @has_emeter_iot
@pytest.mark.requires_dummy() @pytest.mark.requires_dummy()
async def test_get_emeter_monthly(dev): async def test_get_emeter_monthly(dev):
assert dev.has_emeter emeter = dev.modules[Module.Energy]
assert await dev.get_emeter_monthly(year=1900) == {} assert await emeter.get_monthly_stats(year=1900) == {}
d = await dev.get_emeter_monthly() d = await emeter.get_monthly_stats()
assert len(d) > 0 assert len(d) > 0
k, v = d.popitem() k, v = d.popitem()
@ -102,23 +102,26 @@ async def test_get_emeter_monthly(dev):
assert isinstance(v, float) assert isinstance(v, float)
# Test kwh (energy, energy_wh) # Test kwh (energy, energy_wh)
d = await dev.get_emeter_monthly(kwh=False) d = await emeter.get_monthly_stats(kwh=False)
k2, v2 = d.popitem() k2, v2 = d.popitem()
assert v * 1000 == v2 assert v * 1000 == v2
@has_emeter_iot @has_emeter_iot
async def test_emeter_status(dev): async def test_emeter_status(dev):
assert dev.has_emeter emeter = dev.modules[Module.Energy]
d = await dev.get_emeter_realtime() d = await emeter.get_status()
with pytest.raises(KeyError): with pytest.raises(KeyError):
assert d["foo"] assert d["foo"]
assert d["power_mw"] == d["power"] * 1000 assert d["power_mw"] == d["power"] * 1000
# bulbs have only power according to tplink simulator. # bulbs have only power according to tplink simulator.
if not dev.is_bulb and not dev.is_light_strip: if (
dev.device_type is not DeviceType.Bulb
and dev.device_type is not DeviceType.LightStrip
):
assert d["voltage_mv"] == d["voltage"] * 1000 assert d["voltage_mv"] == d["voltage"] * 1000
assert d["current_ma"] == d["current"] * 1000 assert d["current_ma"] == d["current"] * 1000
@ -128,19 +131,17 @@ async def test_emeter_status(dev):
@pytest.mark.skip("not clearing your stats..") @pytest.mark.skip("not clearing your stats..")
@has_emeter @has_emeter
async def test_erase_emeter_stats(dev): async def test_erase_emeter_stats(dev):
assert dev.has_emeter emeter = dev.modules[Module.Energy]
await dev.erase_emeter() await emeter.erase_emeter()
@has_emeter_iot @has_emeter_iot
async def test_current_consumption(dev): async def test_current_consumption(dev):
if dev.has_emeter: emeter = dev.modules[Module.Energy]
x = dev.current_consumption x = emeter.current_consumption
assert isinstance(x, float) assert isinstance(x, float)
assert x >= 0.0 assert x >= 0.0
else:
assert dev.current_consumption is None
async def test_emeterstatus_missing_current(): async def test_emeterstatus_missing_current():
@ -180,7 +181,7 @@ async def test_emeter_daily():
emeter_data["get_daystat"]["day_list"].append( emeter_data["get_daystat"]["day_list"].append(
{"day": now.day, "energy_wh": 500, "month": now.month, "year": now.year} {"day": now.day, "energy_wh": 500, "month": now.month, "year": now.year}
) )
assert emeter.emeter_today == 0.500 assert emeter.consumption_today == 0.500
@has_emeter @has_emeter

View File

@ -16,7 +16,7 @@ from voluptuous import (
Schema, Schema,
) )
from kasa import KasaException, Module from kasa import DeviceType, KasaException, Module
from kasa.iot import IotDevice from kasa.iot import IotDevice
from kasa.iot.iotmodule import _merge_dict from kasa.iot.iotmodule import _merge_dict
@ -92,10 +92,8 @@ async def test_state_info(dev):
@pytest.mark.requires_dummy() @pytest.mark.requires_dummy()
@device_iot @device_iot
async def test_invalid_connection(mocker, dev): async def test_invalid_connection(mocker, dev):
with ( mocker.patch.object(FakeIotProtocol, "query", side_effect=KasaException)
mocker.patch.object(FakeIotProtocol, "query", side_effect=KasaException), with pytest.raises(KasaException):
pytest.raises(KasaException),
):
await dev.update() await dev.update()
@ -169,7 +167,7 @@ async def test_state(dev, turn_on):
async def test_on_since(dev, turn_on): async def test_on_since(dev, turn_on):
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
orig_state = dev.is_on orig_state = dev.is_on
if "on_time" not in dev.sys_info and not dev.is_strip: if "on_time" not in dev.sys_info and dev.device_type is not DeviceType.Strip:
assert dev.on_since is None assert dev.on_since is None
elif orig_state: elif orig_state:
assert isinstance(dev.on_since, datetime) assert isinstance(dev.on_since, datetime)
@ -179,7 +177,7 @@ async def test_on_since(dev, turn_on):
@device_iot @device_iot
async def test_time(dev): async def test_time(dev):
assert isinstance(await dev.get_time(), datetime) assert isinstance(dev.modules[Module.Time].time, datetime)
@device_iot @device_iot
@ -216,7 +214,7 @@ async def test_representation(dev):
@device_iot @device_iot
async def test_children(dev): async def test_children(dev):
"""Make sure that children property is exposed by every device.""" """Make sure that children property is exposed by every device."""
if dev.is_strip: if dev.device_type is DeviceType.Strip:
assert len(dev.children) > 0 assert len(dev.children) > 0
else: else:
assert len(dev.children) == 0 assert len(dev.children) == 0

View File

@ -1,35 +1,37 @@
import pytest import pytest
from kasa import DeviceType from kasa import DeviceType, Module
from kasa.iot import IotLightStrip from kasa.iot import IotLightStrip
from kasa.iot.modules import LightEffect
from .conftest import lightstrip_iot from .conftest import lightstrip_iot
@lightstrip_iot @lightstrip_iot
async def test_lightstrip_length(dev: IotLightStrip): async def test_lightstrip_length(dev: IotLightStrip):
assert dev.is_light_strip
assert dev.device_type == DeviceType.LightStrip assert dev.device_type == DeviceType.LightStrip
assert dev.length == dev.sys_info["length"] assert dev.length == dev.sys_info["length"]
@lightstrip_iot @lightstrip_iot
async def test_lightstrip_effect(dev: IotLightStrip): async def test_lightstrip_effect(dev: IotLightStrip):
assert isinstance(dev.effect, dict) le: LightEffect = dev.modules[Module.LightEffect]
assert isinstance(le._deprecated_effect, dict)
for k in ["brightness", "custom", "enable", "id", "name"]: for k in ["brightness", "custom", "enable", "id", "name"]:
assert k in dev.effect assert k in le._deprecated_effect
@lightstrip_iot @lightstrip_iot
async def test_effects_lightstrip_set_effect(dev: IotLightStrip): async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
le: LightEffect = dev.modules[Module.LightEffect]
with pytest.raises( with pytest.raises(
ValueError, match="The effect Not real is not a built in effect" ValueError, match="The effect Not real is not a built in effect"
): ):
await dev.set_effect("Not real") await le.set_effect("Not real")
await dev.set_effect("Candy Cane") await le.set_effect("Candy Cane")
await dev.update() await dev.update()
assert dev.effect["name"] == "Candy Cane" assert le.effect == "Candy Cane"
@lightstrip_iot @lightstrip_iot
@ -38,12 +40,13 @@ async def test_effects_lightstrip_set_effect_brightness(
dev: IotLightStrip, brightness, mocker dev: IotLightStrip, brightness, mocker
): ):
query_helper = mocker.patch("kasa.iot.IotLightStrip._query_helper") query_helper = mocker.patch("kasa.iot.IotLightStrip._query_helper")
le: LightEffect = dev.modules[Module.LightEffect]
# test that default brightness works (100 for candy cane) # test that default brightness works (100 for candy cane)
if brightness == 100: if brightness == 100:
await dev.set_effect("Candy Cane") await le.set_effect("Candy Cane")
else: else:
await dev.set_effect("Candy Cane", brightness=brightness) await le.set_effect("Candy Cane", brightness=brightness)
args, kwargs = query_helper.call_args_list[0] args, kwargs = query_helper.call_args_list[0]
payload = args[2] payload = args[2]
@ -56,12 +59,13 @@ async def test_effects_lightstrip_set_effect_transition(
dev: IotLightStrip, transition, mocker dev: IotLightStrip, transition, mocker
): ):
query_helper = mocker.patch("kasa.iot.IotLightStrip._query_helper") query_helper = mocker.patch("kasa.iot.IotLightStrip._query_helper")
le: LightEffect = dev.modules[Module.LightEffect]
# test that default (500 for candy cane) transition works # test that default (500 for candy cane) transition works
if transition == 500: if transition == 500:
await dev.set_effect("Candy Cane") await le.set_effect("Candy Cane")
else: else:
await dev.set_effect("Candy Cane", transition=transition) await le.set_effect("Candy Cane", transition=transition)
args, kwargs = query_helper.call_args_list[0] args, kwargs = query_helper.call_args_list[0]
payload = args[2] payload = args[2]
@ -70,8 +74,9 @@ async def test_effects_lightstrip_set_effect_transition(
@lightstrip_iot @lightstrip_iot
async def test_effects_lightstrip_has_effects(dev: IotLightStrip): async def test_effects_lightstrip_has_effects(dev: IotLightStrip):
assert dev.has_effects is True le: LightEffect = dev.modules[Module.LightEffect]
assert dev.effect_list assert le is not None
assert le.effect_list
@lightstrip_iot @lightstrip_iot

View File

@ -1,3 +1,5 @@
import pytest
from kasa import DeviceType from kasa import DeviceType
from .conftest import plug, plug_iot, plug_smart, switch_smart, wallswitch_iot from .conftest import plug, plug_iot, plug_smart, switch_smart, wallswitch_iot
@ -16,7 +18,6 @@ async def test_plug_sysinfo(dev):
assert dev.model is not None assert dev.model is not None
assert dev.device_type == DeviceType.Plug or dev.device_type == DeviceType.Strip assert dev.device_type == DeviceType.Plug or dev.device_type == DeviceType.Strip
assert dev.is_plug or dev.is_strip
@wallswitch_iot @wallswitch_iot
@ -27,11 +28,11 @@ async def test_switch_sysinfo(dev):
assert dev.model is not None assert dev.model is not None
assert dev.device_type == DeviceType.WallSwitch assert dev.device_type == DeviceType.WallSwitch
assert dev.is_wallswitch
@plug_iot @plug_iot
async def test_plug_led(dev): async def test_plug_led(dev):
with pytest.deprecated_call(match="use: Module.Led in device.modules instead"):
original = dev.led original = dev.led
await dev.set_led(False) await dev.set_led(False)
@ -47,6 +48,7 @@ async def test_plug_led(dev):
@wallswitch_iot @wallswitch_iot
async def test_switch_led(dev): async def test_switch_led(dev):
with pytest.deprecated_call(match="use: Module.Led in device.modules instead"):
original = dev.led original = dev.led
await dev.set_led(False) await dev.set_led(False)

View File

@ -45,10 +45,8 @@ async def test_update_no_device_info(dev: SmartDevice, mocker: MockerFixture):
"get_device_time": {}, "get_device_time": {},
} }
msg = f"get_device_info not found in {mock_response} for device 127.0.0.123" msg = f"get_device_info not found in {mock_response} for device 127.0.0.123"
with ( mocker.patch.object(dev.protocol, "query", return_value=mock_response)
mocker.patch.object(dev.protocol, "query", return_value=mock_response), with pytest.raises(KasaException, match=msg):
pytest.raises(KasaException, match=msg),
):
await dev.update() await dev.update()

View File

@ -325,6 +325,7 @@ async def test_smart_protocol_lists_single_request(mocker, list_sum, batch_size)
"foobar", "foobar",
list_return_size=batch_size, list_return_size=batch_size,
component_nego_not_included=True, component_nego_not_included=True,
get_child_fixtures=False,
) )
protocol = SmartProtocol(transport=ft) protocol = SmartProtocol(transport=ft)
query_spy = mocker.spy(protocol, "_execute_query") query_spy = mocker.spy(protocol, "_execute_query")
@ -357,6 +358,7 @@ async def test_smart_protocol_lists_multiple_request(mocker, list_sum, batch_siz
"foobar", "foobar",
list_return_size=batch_size, list_return_size=batch_size,
component_nego_not_included=True, component_nego_not_included=True,
get_child_fixtures=False,
) )
protocol = SmartProtocol(transport=ft) protocol = SmartProtocol(transport=ft)
query_spy = mocker.spy(protocol, "_execute_query") query_spy = mocker.spy(protocol, "_execute_query")