mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
Follow main package structure for tests (#1317)
* Transport tests under tests/transports/ * Protocol tests under tests/protocols/ * IOT tests under iot/ * Plus some minor cleanups, most code changes are related to splitting up smart & iot tests
This commit is contained in:
parent
6adb2b5c28
commit
fcb604e435
@ -217,6 +217,9 @@ no_emeter = parametrize(
|
|||||||
model_filter=ALL_DEVICES - WITH_EMETER,
|
model_filter=ALL_DEVICES - WITH_EMETER,
|
||||||
protocol_filter={"SMART", "IOT"},
|
protocol_filter={"SMART", "IOT"},
|
||||||
)
|
)
|
||||||
|
has_emeter_smart = parametrize(
|
||||||
|
"has emeter smart", model_filter=WITH_EMETER_SMART, protocol_filter={"SMART"}
|
||||||
|
)
|
||||||
has_emeter_iot = parametrize(
|
has_emeter_iot = parametrize(
|
||||||
"has emeter iot", model_filter=WITH_EMETER_IOT, protocol_filter={"IOT"}
|
"has emeter iot", model_filter=WITH_EMETER_IOT, protocol_filter={"IOT"}
|
||||||
)
|
)
|
||||||
|
@ -12,13 +12,9 @@ from voluptuous import (
|
|||||||
|
|
||||||
from kasa import Device, DeviceType, 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 IotStrip
|
||||||
from kasa.iot.modules.emeter import Emeter
|
from kasa.iot.modules.emeter import Emeter
|
||||||
from kasa.smart import SmartDevice
|
from tests.conftest import has_emeter_iot, no_emeter_iot
|
||||||
from kasa.smart.modules import Energy as SmartEnergyModule
|
|
||||||
from kasa.smart.smartmodule import SmartModule
|
|
||||||
|
|
||||||
from .conftest import has_emeter, has_emeter_iot, no_emeter
|
|
||||||
|
|
||||||
CURRENT_CONSUMPTION_SCHEMA = Schema(
|
CURRENT_CONSUMPTION_SCHEMA = Schema(
|
||||||
Any(
|
Any(
|
||||||
@ -40,30 +36,23 @@ CURRENT_CONSUMPTION_SCHEMA = Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@no_emeter
|
@no_emeter_iot
|
||||||
async def test_no_emeter(dev):
|
async def test_no_emeter(dev):
|
||||||
assert not dev.has_emeter
|
assert not dev.has_emeter
|
||||||
|
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
await dev.get_emeter_realtime()
|
await dev.get_emeter_realtime()
|
||||||
# Only iot devices support the historical stats so other
|
|
||||||
# devices will not implement the methods below
|
with pytest.raises(AttributeError):
|
||||||
if isinstance(dev, IotDevice):
|
await dev.get_emeter_daily()
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
await dev.get_emeter_daily()
|
await dev.get_emeter_monthly()
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
await dev.get_emeter_monthly()
|
await dev.erase_emeter_stats()
|
||||||
with pytest.raises(AttributeError):
|
|
||||||
await dev.erase_emeter_stats()
|
|
||||||
|
|
||||||
|
|
||||||
@has_emeter
|
@has_emeter_iot
|
||||||
async def test_get_emeter_realtime(dev):
|
async def test_get_emeter_realtime(dev):
|
||||||
if isinstance(dev, SmartDevice):
|
|
||||||
mod = SmartEnergyModule(dev, str(Module.Energy))
|
|
||||||
if not await mod._check_supported():
|
|
||||||
pytest.skip(f"Energy module not supported for {dev}.")
|
|
||||||
|
|
||||||
emeter = dev.modules[Module.Energy]
|
emeter = dev.modules[Module.Energy]
|
||||||
|
|
||||||
current_emeter = await emeter.get_status()
|
current_emeter = await emeter.get_status()
|
||||||
@ -136,7 +125,7 @@ async def test_emeter_status(dev):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip("not clearing your stats..")
|
@pytest.mark.skip("not clearing your stats..")
|
||||||
@has_emeter
|
@has_emeter_iot
|
||||||
async def test_erase_emeter_stats(dev):
|
async def test_erase_emeter_stats(dev):
|
||||||
emeter = dev.modules[Module.Energy]
|
emeter = dev.modules[Module.Energy]
|
||||||
|
|
||||||
@ -191,37 +180,22 @@ async def test_emeter_daily():
|
|||||||
assert emeter.consumption_today == 0.500
|
assert emeter.consumption_today == 0.500
|
||||||
|
|
||||||
|
|
||||||
@has_emeter
|
@has_emeter_iot
|
||||||
async def test_supported(dev: Device):
|
async def test_supported(dev: Device):
|
||||||
if isinstance(dev, SmartDevice):
|
|
||||||
mod = SmartEnergyModule(dev, str(Module.Energy))
|
|
||||||
if not await mod._check_supported():
|
|
||||||
pytest.skip(f"Energy module not supported for {dev}.")
|
|
||||||
energy_module = dev.modules.get(Module.Energy)
|
energy_module = dev.modules.get(Module.Energy)
|
||||||
assert energy_module
|
assert energy_module
|
||||||
|
|
||||||
if isinstance(dev, IotDevice):
|
info = (
|
||||||
info = (
|
dev._last_update
|
||||||
dev._last_update
|
if not isinstance(dev, IotStrip)
|
||||||
if not isinstance(dev, IotStrip)
|
else dev.children[0].internal_state
|
||||||
else dev.children[0].internal_state
|
)
|
||||||
)
|
emeter = info[energy_module._module]["get_realtime"]
|
||||||
emeter = info[energy_module._module]["get_realtime"]
|
has_total = "total" in emeter or "total_wh" in emeter
|
||||||
has_total = "total" in emeter or "total_wh" in emeter
|
has_voltage_current = "voltage" in emeter or "voltage_mv" in emeter
|
||||||
has_voltage_current = "voltage" in emeter or "voltage_mv" in emeter
|
assert energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is has_total
|
||||||
assert (
|
assert (
|
||||||
energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is has_total
|
energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT)
|
||||||
)
|
is has_voltage_current
|
||||||
assert (
|
)
|
||||||
energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT)
|
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is True
|
||||||
is has_voltage_current
|
|
||||||
)
|
|
||||||
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is True
|
|
||||||
else:
|
|
||||||
assert isinstance(energy_module, SmartModule)
|
|
||||||
assert energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is False
|
|
||||||
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is False
|
|
||||||
if energy_module.supported_version < 2:
|
|
||||||
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is False
|
|
||||||
else:
|
|
||||||
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is True
|
|
320
tests/iot/test_iotbulb.py
Normal file
320
tests/iot/test_iotbulb.py
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from voluptuous import (
|
||||||
|
All,
|
||||||
|
Boolean,
|
||||||
|
Optional,
|
||||||
|
Range,
|
||||||
|
Schema,
|
||||||
|
)
|
||||||
|
|
||||||
|
from kasa import Device, IotLightPreset, KasaException, LightState, Module
|
||||||
|
from kasa.iot import IotBulb, IotDimmer
|
||||||
|
from kasa.iot.modules import LightPreset as IotLightPresetModule
|
||||||
|
from tests.conftest import (
|
||||||
|
bulb_iot,
|
||||||
|
color_bulb_iot,
|
||||||
|
dimmable_iot,
|
||||||
|
handle_turn_on,
|
||||||
|
non_dimmable_iot,
|
||||||
|
turn_on,
|
||||||
|
variable_temp_iot,
|
||||||
|
)
|
||||||
|
from tests.iot.test_iotdevice import SYSINFO_SCHEMA
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_bulb_sysinfo(dev: Device):
|
||||||
|
assert dev.sys_info is not None
|
||||||
|
SYSINFO_SCHEMA_BULB(dev.sys_info)
|
||||||
|
|
||||||
|
assert dev.model is not None
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_light_state_without_update(dev: IotBulb, monkeypatch):
|
||||||
|
monkeypatch.setitem(dev._last_update["system"]["get_sysinfo"], "light_state", None)
|
||||||
|
with pytest.raises(KasaException):
|
||||||
|
print(dev.light_state)
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_get_light_state(dev: IotBulb):
|
||||||
|
LIGHT_STATE_SCHEMA(await dev.get_light_state())
|
||||||
|
|
||||||
|
|
||||||
|
@color_bulb_iot
|
||||||
|
async def test_set_hsv_transition(dev: IotBulb, mocker):
|
||||||
|
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
|
||||||
|
light = dev.modules.get(Module.Light)
|
||||||
|
assert light
|
||||||
|
await light.set_hsv(10, 10, 100, transition=1000)
|
||||||
|
|
||||||
|
set_light_state.assert_called_with(
|
||||||
|
{"hue": 10, "saturation": 10, "brightness": 100, "color_temp": 0},
|
||||||
|
transition=1000,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_light_set_state(dev: IotBulb, mocker):
|
||||||
|
"""Testing setting LightState on the light module."""
|
||||||
|
light = dev.modules.get(Module.Light)
|
||||||
|
assert light
|
||||||
|
set_light_state = mocker.spy(dev, "_set_light_state")
|
||||||
|
state = LightState(light_on=True)
|
||||||
|
await light.set_state(state)
|
||||||
|
|
||||||
|
set_light_state.assert_called_with({"on_off": 1}, transition=None)
|
||||||
|
state = LightState(light_on=False)
|
||||||
|
await light.set_state(state)
|
||||||
|
|
||||||
|
set_light_state.assert_called_with({"on_off": 0}, transition=None)
|
||||||
|
|
||||||
|
|
||||||
|
@variable_temp_iot
|
||||||
|
async def test_set_color_temp_transition(dev: IotBulb, mocker):
|
||||||
|
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
@variable_temp_iot
|
||||||
|
@pytest.mark.xdist_group(name="caplog")
|
||||||
|
async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
|
||||||
|
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
|
||||||
|
light = dev.modules.get(Module.Light)
|
||||||
|
assert light
|
||||||
|
assert light.valid_temperature_range == (2700, 5000)
|
||||||
|
assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@dimmable_iot
|
||||||
|
@turn_on
|
||||||
|
async def test_dimmable_brightness(dev: IotBulb, turn_on):
|
||||||
|
assert isinstance(dev, IotBulb | IotDimmer)
|
||||||
|
light = dev.modules.get(Module.Light)
|
||||||
|
assert light
|
||||||
|
await handle_turn_on(dev, turn_on)
|
||||||
|
assert dev._is_dimmable
|
||||||
|
|
||||||
|
await light.set_brightness(50)
|
||||||
|
await dev.update()
|
||||||
|
assert light.brightness == 50
|
||||||
|
|
||||||
|
await light.set_brightness(10)
|
||||||
|
await dev.update()
|
||||||
|
assert light.brightness == 10
|
||||||
|
|
||||||
|
with pytest.raises(TypeError, match="Brightness must be an integer"):
|
||||||
|
await light.set_brightness("foo") # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_turn_on_transition(dev: IotBulb, mocker):
|
||||||
|
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
|
||||||
|
await dev.turn_on(transition=1000)
|
||||||
|
|
||||||
|
set_light_state.assert_called_with({"on_off": 1}, transition=1000)
|
||||||
|
|
||||||
|
await dev.turn_off(transition=100)
|
||||||
|
|
||||||
|
set_light_state.assert_called_with({"on_off": 0}, transition=100)
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
|
||||||
|
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
@dimmable_iot
|
||||||
|
async def test_invalid_brightness(dev: IotBulb):
|
||||||
|
assert dev._is_dimmable
|
||||||
|
light = dev.modules.get(Module.Light)
|
||||||
|
assert light
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match=re.escape("Invalid brightness value: 110 (valid range: 0-100%)"),
|
||||||
|
):
|
||||||
|
await light.set_brightness(110)
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match=re.escape("Invalid brightness value: -100 (valid range: 0-100%)"),
|
||||||
|
):
|
||||||
|
await light.set_brightness(-100)
|
||||||
|
|
||||||
|
|
||||||
|
@non_dimmable_iot
|
||||||
|
async def test_non_dimmable(dev: IotBulb):
|
||||||
|
assert not dev._is_dimmable
|
||||||
|
light = dev.modules.get(Module.Light)
|
||||||
|
assert light
|
||||||
|
with pytest.raises(KasaException):
|
||||||
|
assert light.brightness == 0
|
||||||
|
with pytest.raises(KasaException):
|
||||||
|
await light.set_brightness(100)
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_ignore_default_not_set_without_color_mode_change_turn_on(
|
||||||
|
dev: IotBulb, mocker
|
||||||
|
):
|
||||||
|
query_helper = mocker.patch("kasa.iot.IotBulb._query_helper")
|
||||||
|
# When turning back without settings, ignore default to restore the state
|
||||||
|
await dev.turn_on()
|
||||||
|
args, kwargs = query_helper.call_args_list[0]
|
||||||
|
assert args[2] == {"on_off": 1, "ignore_default": 0}
|
||||||
|
|
||||||
|
await dev.turn_off()
|
||||||
|
args, kwargs = query_helper.call_args_list[1]
|
||||||
|
assert args[2] == {"on_off": 0, "ignore_default": 1}
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_list_presets(dev: IotBulb):
|
||||||
|
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
|
||||||
|
# are handled by the LightEffect module so exclude preferred states with id
|
||||||
|
raw_presets = [
|
||||||
|
pstate for pstate in dev.sys_info["preferred_state"] if "id" not in pstate
|
||||||
|
]
|
||||||
|
assert len(presets) == len(raw_presets)
|
||||||
|
|
||||||
|
for preset, raw in zip(presets, raw_presets, strict=False):
|
||||||
|
assert preset.index == raw["index"]
|
||||||
|
assert preset.brightness == raw["brightness"]
|
||||||
|
assert preset.hue == raw["hue"]
|
||||||
|
assert preset.saturation == raw["saturation"]
|
||||||
|
assert preset.color_temp == raw["color_temp"]
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_modify_preset(dev: IotBulb, mocker):
|
||||||
|
"""Verify that modifying preset calls the and exceptions are raised properly."""
|
||||||
|
if (
|
||||||
|
not (light_preset := dev.modules.get(Module.LightPreset))
|
||||||
|
or not light_preset._deprecated_presets
|
||||||
|
):
|
||||||
|
pytest.skip("Some strips do not support presets")
|
||||||
|
|
||||||
|
assert isinstance(light_preset, IotLightPresetModule)
|
||||||
|
data: dict[str, int | None] = {
|
||||||
|
"index": 0,
|
||||||
|
"brightness": 10,
|
||||||
|
"hue": 0,
|
||||||
|
"saturation": 0,
|
||||||
|
"color_temp": 0,
|
||||||
|
}
|
||||||
|
preset = IotLightPreset(**data) # type: ignore[call-arg, arg-type]
|
||||||
|
|
||||||
|
assert preset.index == 0
|
||||||
|
assert preset.brightness == 10
|
||||||
|
assert preset.hue == 0
|
||||||
|
assert preset.saturation == 0
|
||||||
|
assert preset.color_temp == 0
|
||||||
|
|
||||||
|
await light_preset._deprecated_save_preset(preset)
|
||||||
|
await dev.update()
|
||||||
|
assert light_preset._deprecated_presets[0].brightness == 10
|
||||||
|
|
||||||
|
with pytest.raises(KasaException):
|
||||||
|
await light_preset._deprecated_save_preset(
|
||||||
|
IotLightPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0) # type: ignore[call-arg]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("preset", "payload"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
IotLightPreset(index=0, hue=0, brightness=1, saturation=0), # type: ignore[call-arg]
|
||||||
|
{"index": 0, "hue": 0, "brightness": 1, "saturation": 0},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
IotLightPreset(index=0, brightness=1, id="testid", mode=2, custom=0), # type: ignore[call-arg]
|
||||||
|
{"index": 0, "brightness": 1, "id": "testid", "mode": 2, "custom": 0},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_modify_preset_payloads(dev: IotBulb, preset, payload, mocker):
|
||||||
|
"""Test that modify preset payloads ignore none values."""
|
||||||
|
if (
|
||||||
|
not (light_preset := dev.modules.get(Module.LightPreset))
|
||||||
|
or not light_preset._deprecated_presets
|
||||||
|
):
|
||||||
|
pytest.skip("Some strips do not support presets")
|
||||||
|
|
||||||
|
query_helper = mocker.patch("kasa.iot.IotBulb._query_helper")
|
||||||
|
await light_preset._deprecated_save_preset(preset)
|
||||||
|
query_helper.assert_called_with(dev.LIGHT_SERVICE, "set_preferred_state", payload)
|
||||||
|
|
||||||
|
|
||||||
|
LIGHT_STATE_SCHEMA = Schema(
|
||||||
|
{
|
||||||
|
"brightness": All(int, Range(min=0, max=100)),
|
||||||
|
"color_temp": int,
|
||||||
|
"hue": All(int, Range(min=0, max=360)),
|
||||||
|
"mode": str,
|
||||||
|
"on_off": Boolean,
|
||||||
|
"saturation": All(int, Range(min=0, max=100)),
|
||||||
|
"length": Optional(int),
|
||||||
|
"transition": Optional(int),
|
||||||
|
"dft_on_state": Optional(
|
||||||
|
{
|
||||||
|
"brightness": All(int, Range(min=0, max=100)),
|
||||||
|
"color_temp": All(int, Range(min=0, max=9000)),
|
||||||
|
"hue": All(int, Range(min=0, max=360)),
|
||||||
|
"mode": str,
|
||||||
|
"saturation": All(int, Range(min=0, max=100)),
|
||||||
|
"groups": Optional(list[int]),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"err_code": int,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SYSINFO_SCHEMA_BULB = SYSINFO_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
"ctrl_protocols": Optional(dict),
|
||||||
|
"description": Optional(str), # Seen on LBxxx, similar to dev_name
|
||||||
|
"dev_state": str,
|
||||||
|
"disco_ver": str,
|
||||||
|
"heapsize": int,
|
||||||
|
"is_color": Boolean,
|
||||||
|
"is_dimmable": Boolean,
|
||||||
|
"is_factory": Boolean,
|
||||||
|
"is_variable_color_temp": Boolean,
|
||||||
|
"light_state": LIGHT_STATE_SCHEMA,
|
||||||
|
"preferred_state": [
|
||||||
|
{
|
||||||
|
"brightness": All(int, Range(min=0, max=100)),
|
||||||
|
"color_temp": int,
|
||||||
|
"hue": All(int, Range(min=0, max=360)),
|
||||||
|
"index": int,
|
||||||
|
"saturation": All(int, Range(min=0, max=100)),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bulb_iot
|
||||||
|
async def test_turn_on_behaviours(dev: IotBulb):
|
||||||
|
behavior = await dev.get_turn_on_behavior()
|
||||||
|
assert behavior
|
@ -19,10 +19,9 @@ from voluptuous import (
|
|||||||
from kasa import DeviceType, 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
|
||||||
|
from tests.conftest import get_device_for_fixture_protocol, handle_turn_on, turn_on
|
||||||
from .conftest import get_device_for_fixture_protocol, handle_turn_on, turn_on
|
from tests.device_fixtures import device_iot, has_emeter_iot, no_emeter_iot
|
||||||
from .device_fixtures import device_iot, has_emeter_iot, no_emeter_iot
|
from tests.fakeprotocol_iot import FakeIotProtocol
|
||||||
from .fakeprotocol_iot import FakeIotProtocol
|
|
||||||
|
|
||||||
TZ_SCHEMA = Schema(
|
TZ_SCHEMA = Schema(
|
||||||
{"zone_str": str, "dst_offset": int, "index": All(int, Range(min=0)), "tz_str": str}
|
{"zone_str": str, "dst_offset": int, "index": All(int, Range(min=0)), "tz_str": str}
|
@ -2,8 +2,7 @@ import pytest
|
|||||||
|
|
||||||
from kasa import DeviceType, Module
|
from kasa import DeviceType, Module
|
||||||
from kasa.iot import IotDimmer
|
from kasa.iot import IotDimmer
|
||||||
|
from tests.conftest import dimmer_iot, handle_turn_on, turn_on
|
||||||
from .conftest import dimmer_iot, handle_turn_on, turn_on
|
|
||||||
|
|
||||||
|
|
||||||
@dimmer_iot
|
@dimmer_iot
|
@ -3,8 +3,7 @@ import pytest
|
|||||||
from kasa import DeviceType, Module
|
from kasa import DeviceType, Module
|
||||||
from kasa.iot import IotLightStrip
|
from kasa.iot import IotLightStrip
|
||||||
from kasa.iot.modules import LightEffect
|
from kasa.iot.modules import LightEffect
|
||||||
|
from tests.conftest import lightstrip_iot
|
||||||
from .conftest import lightstrip_iot
|
|
||||||
|
|
||||||
|
|
||||||
@lightstrip_iot
|
@lightstrip_iot
|
0
tests/protocols/__init__.py
Normal file
0
tests/protocols/__init__.py
Normal file
@ -29,8 +29,8 @@ from kasa.transports.basetransport import BaseTransport
|
|||||||
from kasa.transports.klaptransport import KlapTransport, KlapTransportV2
|
from kasa.transports.klaptransport import KlapTransport, KlapTransportV2
|
||||||
from kasa.transports.xortransport import XorEncryption, XorTransport
|
from kasa.transports.xortransport import XorEncryption, XorTransport
|
||||||
|
|
||||||
from .conftest import device_iot
|
from ..conftest import device_iot
|
||||||
from .fakeprotocol_iot import FakeIotTransport
|
from ..fakeprotocol_iot import FakeIotTransport
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
@ -12,8 +12,8 @@ from kasa.exceptions import (
|
|||||||
from kasa.protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper
|
from kasa.protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper
|
||||||
from kasa.smart import SmartDevice
|
from kasa.smart import SmartDevice
|
||||||
|
|
||||||
from .conftest import device_smart
|
from ..conftest import device_smart
|
||||||
from .fakeprotocol_smart import FakeSmartTransport
|
from ..fakeprotocol_smart import FakeSmartTransport
|
||||||
|
|
||||||
DUMMY_QUERY = {"foobar": {"foo": "bar", "bar": "foo"}}
|
DUMMY_QUERY = {"foobar": {"foo": "bar", "bar": "foo"}}
|
||||||
DUMMY_MULTIPLE_QUERY = {
|
DUMMY_MULTIPLE_QUERY = {
|
21
tests/smart/modules/test_energy.py
Normal file
21
tests/smart/modules/test_energy.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from kasa import Module, SmartDevice
|
||||||
|
from kasa.interfaces.energy import Energy
|
||||||
|
from kasa.smart.modules import Energy as SmartEnergyModule
|
||||||
|
from tests.conftest import has_emeter_smart
|
||||||
|
|
||||||
|
|
||||||
|
@has_emeter_smart
|
||||||
|
async def test_supported(dev: SmartDevice):
|
||||||
|
energy_module = dev.modules.get(Module.Energy)
|
||||||
|
if not energy_module:
|
||||||
|
pytest.skip(f"Energy module not supported for {dev}.")
|
||||||
|
|
||||||
|
assert isinstance(energy_module, SmartEnergyModule)
|
||||||
|
assert energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is False
|
||||||
|
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is False
|
||||||
|
if energy_module.supported_version < 2:
|
||||||
|
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is False
|
||||||
|
else:
|
||||||
|
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is True
|
@ -17,12 +17,12 @@ from kasa.protocols.smartprotocol import _ChildProtocolWrapper
|
|||||||
from kasa.smart import SmartDevice
|
from kasa.smart import SmartDevice
|
||||||
from kasa.smart.modules.energy import Energy
|
from kasa.smart.modules.energy import Energy
|
||||||
from kasa.smart.smartmodule import SmartModule
|
from kasa.smart.smartmodule import SmartModule
|
||||||
|
from tests.conftest import (
|
||||||
from .conftest import (
|
|
||||||
device_smart,
|
device_smart,
|
||||||
get_device_for_fixture_protocol,
|
get_device_for_fixture_protocol,
|
||||||
get_parent_and_child_modules,
|
get_parent_and_child_modules,
|
||||||
)
|
)
|
||||||
|
from tests.device_fixtures import variable_temp_smart
|
||||||
|
|
||||||
|
|
||||||
@device_smart
|
@device_smart
|
||||||
@ -435,3 +435,10 @@ async def test_smartdevice_cloud_connection(dev: SmartDevice, mocker: MockerFixt
|
|||||||
):
|
):
|
||||||
await new_dev.update()
|
await new_dev.update()
|
||||||
assert new_dev.is_cloud_connected is False
|
assert new_dev.is_cloud_connected is False
|
||||||
|
|
||||||
|
|
||||||
|
@variable_temp_smart
|
||||||
|
async def test_smart_temp_range(dev: Device):
|
||||||
|
light = dev.modules.get(Module.Light)
|
||||||
|
assert light
|
||||||
|
assert light.valid_temperature_range
|
@ -1,44 +1,16 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from voluptuous import (
|
|
||||||
All,
|
|
||||||
Boolean,
|
|
||||||
Optional,
|
|
||||||
Range,
|
|
||||||
Schema,
|
|
||||||
)
|
|
||||||
|
|
||||||
from kasa import Device, DeviceType, IotLightPreset, KasaException, LightState, Module
|
from kasa import Device, DeviceType, KasaException, Module
|
||||||
from kasa.iot import IotBulb, IotDimmer
|
from tests.conftest import handle_turn_on, turn_on
|
||||||
from kasa.iot.modules import LightPreset as IotLightPresetModule
|
from tests.device_fixtures import (
|
||||||
|
|
||||||
from .conftest import (
|
|
||||||
bulb,
|
bulb,
|
||||||
bulb_iot,
|
|
||||||
color_bulb,
|
color_bulb,
|
||||||
color_bulb_iot,
|
|
||||||
dimmable_iot,
|
|
||||||
handle_turn_on,
|
|
||||||
non_color_bulb,
|
non_color_bulb,
|
||||||
non_dimmable_iot,
|
|
||||||
non_variable_temp,
|
non_variable_temp,
|
||||||
turn_on,
|
|
||||||
variable_temp,
|
variable_temp,
|
||||||
variable_temp_iot,
|
|
||||||
variable_temp_smart,
|
|
||||||
)
|
)
|
||||||
from .test_iotdevice import SYSINFO_SCHEMA
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_bulb_sysinfo(dev: Device):
|
|
||||||
assert dev.sys_info is not None
|
|
||||||
SYSINFO_SCHEMA_BULB(dev.sys_info)
|
|
||||||
|
|
||||||
assert dev.model is not None
|
|
||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
@ -47,18 +19,6 @@ async def test_state_attributes(dev: Device):
|
|||||||
assert isinstance(dev.state_information["Cloud connection"], bool)
|
assert isinstance(dev.state_information["Cloud connection"], bool)
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_light_state_without_update(dev: IotBulb, monkeypatch):
|
|
||||||
monkeypatch.setitem(dev._last_update["system"]["get_sysinfo"], "light_state", None)
|
|
||||||
with pytest.raises(KasaException):
|
|
||||||
print(dev.light_state)
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_get_light_state(dev: IotBulb):
|
|
||||||
LIGHT_STATE_SCHEMA(await dev.get_light_state())
|
|
||||||
|
|
||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
@turn_on
|
@turn_on
|
||||||
async def test_hsv(dev: Device, turn_on):
|
async def test_hsv(dev: Device, turn_on):
|
||||||
@ -81,35 +41,6 @@ async def test_hsv(dev: Device, turn_on):
|
|||||||
assert brightness == 1
|
assert brightness == 1
|
||||||
|
|
||||||
|
|
||||||
@color_bulb_iot
|
|
||||||
async def test_set_hsv_transition(dev: IotBulb, mocker):
|
|
||||||
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
|
|
||||||
light = dev.modules.get(Module.Light)
|
|
||||||
assert light
|
|
||||||
await light.set_hsv(10, 10, 100, transition=1000)
|
|
||||||
|
|
||||||
set_light_state.assert_called_with(
|
|
||||||
{"hue": 10, "saturation": 10, "brightness": 100, "color_temp": 0},
|
|
||||||
transition=1000,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_light_set_state(dev: IotBulb, mocker):
|
|
||||||
"""Testing setting LightState on the light module."""
|
|
||||||
light = dev.modules.get(Module.Light)
|
|
||||||
assert light
|
|
||||||
set_light_state = mocker.spy(dev, "_set_light_state")
|
|
||||||
state = LightState(light_on=True)
|
|
||||||
await light.set_state(state)
|
|
||||||
|
|
||||||
set_light_state.assert_called_with({"on_off": 1}, transition=None)
|
|
||||||
state = LightState(light_on=False)
|
|
||||||
await light.set_state(state)
|
|
||||||
|
|
||||||
set_light_state.assert_called_with({"on_off": 0}, transition=None)
|
|
||||||
|
|
||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
@turn_on
|
@turn_on
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -221,33 +152,6 @@ async def test_try_set_colortemp(dev: Device, turn_on):
|
|||||||
assert light.color_temp == 2700
|
assert light.color_temp == 2700
|
||||||
|
|
||||||
|
|
||||||
@variable_temp_iot
|
|
||||||
async def test_set_color_temp_transition(dev: IotBulb, mocker):
|
|
||||||
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
@variable_temp_iot
|
|
||||||
@pytest.mark.xdist_group(name="caplog")
|
|
||||||
async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
|
|
||||||
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
|
|
||||||
light = dev.modules.get(Module.Light)
|
|
||||||
assert light
|
|
||||||
assert light.valid_temperature_range == (2700, 5000)
|
|
||||||
assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
@variable_temp_smart
|
|
||||||
async def test_smart_temp_range(dev: Device):
|
|
||||||
light = dev.modules.get(Module.Light)
|
|
||||||
assert light
|
|
||||||
assert light.valid_temperature_range
|
|
||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
async def test_out_of_range_temperature(dev: Device):
|
async def test_out_of_range_temperature(dev: Device):
|
||||||
light = dev.modules.get(Module.Light)
|
light = dev.modules.get(Module.Light)
|
||||||
@ -276,231 +180,6 @@ async def test_non_variable_temp(dev: Device):
|
|||||||
print(light.color_temp)
|
print(light.color_temp)
|
||||||
|
|
||||||
|
|
||||||
@dimmable_iot
|
|
||||||
@turn_on
|
|
||||||
async def test_dimmable_brightness(dev: IotBulb, turn_on):
|
|
||||||
assert isinstance(dev, IotBulb | IotDimmer)
|
|
||||||
light = dev.modules.get(Module.Light)
|
|
||||||
assert light
|
|
||||||
await handle_turn_on(dev, turn_on)
|
|
||||||
assert dev._is_dimmable
|
|
||||||
|
|
||||||
await light.set_brightness(50)
|
|
||||||
await dev.update()
|
|
||||||
assert light.brightness == 50
|
|
||||||
|
|
||||||
await light.set_brightness(10)
|
|
||||||
await dev.update()
|
|
||||||
assert light.brightness == 10
|
|
||||||
|
|
||||||
with pytest.raises(TypeError, match="Brightness must be an integer"):
|
|
||||||
await light.set_brightness("foo") # type: ignore[arg-type]
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_turn_on_transition(dev: IotBulb, mocker):
|
|
||||||
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
|
|
||||||
await dev.turn_on(transition=1000)
|
|
||||||
|
|
||||||
set_light_state.assert_called_with({"on_off": 1}, transition=1000)
|
|
||||||
|
|
||||||
await dev.turn_off(transition=100)
|
|
||||||
|
|
||||||
set_light_state.assert_called_with({"on_off": 0}, transition=100)
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
|
|
||||||
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
@dimmable_iot
|
|
||||||
async def test_invalid_brightness(dev: IotBulb):
|
|
||||||
assert dev._is_dimmable
|
|
||||||
light = dev.modules.get(Module.Light)
|
|
||||||
assert light
|
|
||||||
with pytest.raises(
|
|
||||||
ValueError,
|
|
||||||
match=re.escape("Invalid brightness value: 110 (valid range: 0-100%)"),
|
|
||||||
):
|
|
||||||
await light.set_brightness(110)
|
|
||||||
|
|
||||||
with pytest.raises(
|
|
||||||
ValueError,
|
|
||||||
match=re.escape("Invalid brightness value: -100 (valid range: 0-100%)"),
|
|
||||||
):
|
|
||||||
await light.set_brightness(-100)
|
|
||||||
|
|
||||||
|
|
||||||
@non_dimmable_iot
|
|
||||||
async def test_non_dimmable(dev: IotBulb):
|
|
||||||
assert not dev._is_dimmable
|
|
||||||
light = dev.modules.get(Module.Light)
|
|
||||||
assert light
|
|
||||||
with pytest.raises(KasaException):
|
|
||||||
assert light.brightness == 0
|
|
||||||
with pytest.raises(KasaException):
|
|
||||||
await light.set_brightness(100)
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_ignore_default_not_set_without_color_mode_change_turn_on(
|
|
||||||
dev: IotBulb, mocker
|
|
||||||
):
|
|
||||||
query_helper = mocker.patch("kasa.iot.IotBulb._query_helper")
|
|
||||||
# When turning back without settings, ignore default to restore the state
|
|
||||||
await dev.turn_on()
|
|
||||||
args, kwargs = query_helper.call_args_list[0]
|
|
||||||
assert args[2] == {"on_off": 1, "ignore_default": 0}
|
|
||||||
|
|
||||||
await dev.turn_off()
|
|
||||||
args, kwargs = query_helper.call_args_list[1]
|
|
||||||
assert args[2] == {"on_off": 0, "ignore_default": 1}
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_list_presets(dev: IotBulb):
|
|
||||||
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
|
|
||||||
# are handled by the LightEffect module so exclude preferred states with id
|
|
||||||
raw_presets = [
|
|
||||||
pstate for pstate in dev.sys_info["preferred_state"] if "id" not in pstate
|
|
||||||
]
|
|
||||||
assert len(presets) == len(raw_presets)
|
|
||||||
|
|
||||||
for preset, raw in zip(presets, raw_presets, strict=False):
|
|
||||||
assert preset.index == raw["index"]
|
|
||||||
assert preset.brightness == raw["brightness"]
|
|
||||||
assert preset.hue == raw["hue"]
|
|
||||||
assert preset.saturation == raw["saturation"]
|
|
||||||
assert preset.color_temp == raw["color_temp"]
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_modify_preset(dev: IotBulb, mocker):
|
|
||||||
"""Verify that modifying preset calls the and exceptions are raised properly."""
|
|
||||||
if (
|
|
||||||
not (light_preset := dev.modules.get(Module.LightPreset))
|
|
||||||
or not light_preset._deprecated_presets
|
|
||||||
):
|
|
||||||
pytest.skip("Some strips do not support presets")
|
|
||||||
|
|
||||||
assert isinstance(light_preset, IotLightPresetModule)
|
|
||||||
data: dict[str, int | None] = {
|
|
||||||
"index": 0,
|
|
||||||
"brightness": 10,
|
|
||||||
"hue": 0,
|
|
||||||
"saturation": 0,
|
|
||||||
"color_temp": 0,
|
|
||||||
}
|
|
||||||
preset = IotLightPreset(**data) # type: ignore[call-arg, arg-type]
|
|
||||||
|
|
||||||
assert preset.index == 0
|
|
||||||
assert preset.brightness == 10
|
|
||||||
assert preset.hue == 0
|
|
||||||
assert preset.saturation == 0
|
|
||||||
assert preset.color_temp == 0
|
|
||||||
|
|
||||||
await light_preset._deprecated_save_preset(preset)
|
|
||||||
await dev.update()
|
|
||||||
assert light_preset._deprecated_presets[0].brightness == 10
|
|
||||||
|
|
||||||
with pytest.raises(KasaException):
|
|
||||||
await light_preset._deprecated_save_preset(
|
|
||||||
IotLightPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0) # type: ignore[call-arg]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("preset", "payload"),
|
|
||||||
[
|
|
||||||
(
|
|
||||||
IotLightPreset(index=0, hue=0, brightness=1, saturation=0), # type: ignore[call-arg]
|
|
||||||
{"index": 0, "hue": 0, "brightness": 1, "saturation": 0},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
IotLightPreset(index=0, brightness=1, id="testid", mode=2, custom=0), # type: ignore[call-arg]
|
|
||||||
{"index": 0, "brightness": 1, "id": "testid", "mode": 2, "custom": 0},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_modify_preset_payloads(dev: IotBulb, preset, payload, mocker):
|
|
||||||
"""Test that modify preset payloads ignore none values."""
|
|
||||||
if (
|
|
||||||
not (light_preset := dev.modules.get(Module.LightPreset))
|
|
||||||
or not light_preset._deprecated_presets
|
|
||||||
):
|
|
||||||
pytest.skip("Some strips do not support presets")
|
|
||||||
|
|
||||||
query_helper = mocker.patch("kasa.iot.IotBulb._query_helper")
|
|
||||||
await light_preset._deprecated_save_preset(preset)
|
|
||||||
query_helper.assert_called_with(dev.LIGHT_SERVICE, "set_preferred_state", payload)
|
|
||||||
|
|
||||||
|
|
||||||
LIGHT_STATE_SCHEMA = Schema(
|
|
||||||
{
|
|
||||||
"brightness": All(int, Range(min=0, max=100)),
|
|
||||||
"color_temp": int,
|
|
||||||
"hue": All(int, Range(min=0, max=360)),
|
|
||||||
"mode": str,
|
|
||||||
"on_off": Boolean,
|
|
||||||
"saturation": All(int, Range(min=0, max=100)),
|
|
||||||
"length": Optional(int),
|
|
||||||
"transition": Optional(int),
|
|
||||||
"dft_on_state": Optional(
|
|
||||||
{
|
|
||||||
"brightness": All(int, Range(min=0, max=100)),
|
|
||||||
"color_temp": All(int, Range(min=0, max=9000)),
|
|
||||||
"hue": All(int, Range(min=0, max=360)),
|
|
||||||
"mode": str,
|
|
||||||
"saturation": All(int, Range(min=0, max=100)),
|
|
||||||
"groups": Optional(list[int]),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"err_code": int,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SYSINFO_SCHEMA_BULB = SYSINFO_SCHEMA.extend(
|
|
||||||
{
|
|
||||||
"ctrl_protocols": Optional(dict),
|
|
||||||
"description": Optional(str), # Seen on LBxxx, similar to dev_name
|
|
||||||
"dev_state": str,
|
|
||||||
"disco_ver": str,
|
|
||||||
"heapsize": int,
|
|
||||||
"is_color": Boolean,
|
|
||||||
"is_dimmable": Boolean,
|
|
||||||
"is_factory": Boolean,
|
|
||||||
"is_variable_color_temp": Boolean,
|
|
||||||
"light_state": LIGHT_STATE_SCHEMA,
|
|
||||||
"preferred_state": [
|
|
||||||
{
|
|
||||||
"brightness": All(int, Range(min=0, max=100)),
|
|
||||||
"color_temp": int,
|
|
||||||
"hue": All(int, Range(min=0, max=360)),
|
|
||||||
"index": int,
|
|
||||||
"saturation": All(int, Range(min=0, max=100)),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
def test_device_type_bulb(dev: Device):
|
def test_device_type_bulb(dev: Device):
|
||||||
assert dev.device_type in {DeviceType.Bulb, DeviceType.LightStrip}
|
assert dev.device_type in {DeviceType.Bulb, DeviceType.LightStrip}
|
||||||
|
|
||||||
|
|
||||||
@bulb_iot
|
|
||||||
async def test_turn_on_behaviours(dev: IotBulb):
|
|
||||||
behavior = await dev.get_turn_on_behavior()
|
|
||||||
assert behavior
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from kasa import DeviceType
|
from kasa import DeviceType
|
||||||
|
from tests.iot.test_iotdevice import SYSINFO_SCHEMA
|
||||||
|
|
||||||
from .conftest import plug, plug_iot, plug_smart, switch_smart, wallswitch_iot
|
from .conftest import plug, plug_iot, plug_smart, switch_smart, wallswitch_iot
|
||||||
from .test_iotdevice import SYSINFO_SCHEMA
|
|
||||||
|
|
||||||
# these schemas should go to the mainlib as
|
# these schemas should go to the mainlib as
|
||||||
# they can be useful when adding support for new features/devices
|
# they can be useful when adding support for new features/devices
|
||||||
|
0
tests/transports/__init__.py
Normal file
0
tests/transports/__init__.py
Normal file
Loading…
Reference in New Issue
Block a user