tests: add type annotations to Smart tests (#1686)
Some checks are pending
CI / Perform Lint Checks (3.14) (push) Waiting to run
CI / Python 3.11 on macos-latest (push) Blocked by required conditions
CI / Python 3.12 on macos-latest (push) Blocked by required conditions
CI / Python 3.13 on macos-latest (push) Blocked by required conditions
CI / Python 3.14 on macos-latest (push) Blocked by required conditions
CI / Python 3.11 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.12 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.13 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.14 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.11 on windows-latest (push) Blocked by required conditions
CI / Python 3.12 on windows-latest (push) Blocked by required conditions
CI / Python 3.13 on windows-latest (push) Blocked by required conditions
CI / Python 3.14 on windows-latest (push) Blocked by required conditions
CodeQL Checks / Analyze (python) (push) Waiting to run

Add type annotations across all 'smart' tests, enabling mypy to check test function bodies, catching type errors that were previously hidden.
This commit is contained in:
ZeliardM
2026-07-07 18:11:59 -04:00
committed by GitHub
parent 8840fc395e
commit 88e1c27bd0
30 changed files with 192 additions and 125 deletions

View File

@@ -9,7 +9,7 @@ brightness = parametrize("brightness smart", component_filter="brightness")
@brightness @brightness
async def test_brightness_component(dev: SmartDevice): async def test_brightness_component(dev: SmartDevice) -> None:
"""Test brightness feature.""" """Test brightness feature."""
brightness = next(get_parent_and_child_modules(dev, "Brightness")) brightness = next(get_parent_and_child_modules(dev, "Brightness"))
assert brightness assert brightness
@@ -35,7 +35,7 @@ async def test_brightness_component(dev: SmartDevice):
@dimmable_iot @dimmable_iot
async def test_brightness_dimmable(dev: IotDevice): async def test_brightness_dimmable(dev: IotDevice) -> None:
"""Test brightness feature.""" """Test brightness feature."""
assert isinstance(dev, IotDevice) assert isinstance(dev, IotDevice)
assert "brightness" in dev.sys_info or bool(dev.sys_info["is_dimmable"]) assert "brightness" in dev.sys_info or bool(dev.sys_info["is_dimmable"])

View File

@@ -6,7 +6,7 @@ from ...conftest import variable_temp_smart
@variable_temp_smart @variable_temp_smart
async def test_colortemp_component(dev: SmartDevice): async def test_colortemp_component(dev: SmartDevice) -> None:
"""Test brightness feature.""" """Test brightness feature."""
assert isinstance(dev, SmartDevice) assert isinstance(dev, SmartDevice)
assert "color_temperature" in dev._components assert "color_temperature" in dev._components

View File

@@ -1,17 +1,32 @@
from __future__ import annotations from __future__ import annotations
from typing import TypedDict
import pytest import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from kasa import Module from kasa import Module
from kasa.smart import SmartDevice from kasa.smart import SmartDevice
from kasa.smart.modules import Alarm from kasa.smart.modules import Alarm
from kasa.smart.modules.alarm import AlarmVolume
from ...device_fixtures import get_parent_and_child_modules, parametrize from ...device_fixtures import get_parent_and_child_modules, parametrize
alarm = parametrize("has alarm", component_filter="alarm", protocol_filter={"SMART"}) alarm = parametrize("has alarm", component_filter="alarm", protocol_filter={"SMART"})
class PlayAlarmKwargs(TypedDict, total=False):
duration: int
volume: AlarmVolume | int
sound: str
class PlayAlarmRequestParams(TypedDict, total=False):
alarm_duration: int
alarm_volume: AlarmVolume
alarm_type: str
@alarm @alarm
@pytest.mark.parametrize( @pytest.mark.parametrize(
("feature", "prop_name", "type"), ("feature", "prop_name", "type"),
@@ -23,7 +38,9 @@ alarm = parametrize("has alarm", component_filter="alarm", protocol_filter={"SMA
("alarm_volume_level", "alarm_volume", int), ("alarm_volume_level", "alarm_volume", int),
], ],
) )
async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) alarm = next(get_parent_and_child_modules(dev, Module.Alarm))
assert alarm is not None assert alarm is not None
@@ -37,7 +54,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty
@alarm @alarm
async def test_volume_feature(dev: SmartDevice): async def test_volume_feature(dev: SmartDevice) -> None:
"""Test that volume features have correct choices and range.""" """Test that volume features have correct choices and range."""
alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) alarm = next(get_parent_and_child_modules(dev, Module.Alarm))
assert alarm is not None assert alarm is not None
@@ -64,7 +81,12 @@ async def test_volume_feature(dev: SmartDevice):
), ),
], ],
) )
async def test_play(dev: SmartDevice, kwargs, request_params, mocker: MockerFixture): async def test_play(
dev: SmartDevice,
kwargs: PlayAlarmKwargs,
request_params: PlayAlarmRequestParams,
mocker: MockerFixture,
) -> None:
"""Test that play parameters are handled correctly.""" """Test that play parameters are handled correctly."""
alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm))
call_spy = mocker.spy(alarm, "call") call_spy = mocker.spy(alarm, "call")
@@ -86,7 +108,7 @@ async def test_play(dev: SmartDevice, kwargs, request_params, mocker: MockerFixt
@alarm @alarm
async def test_stop(dev: SmartDevice, mocker: MockerFixture): async def test_stop(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test that stop creates the correct call.""" """Test that stop creates the correct call."""
alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm))
call_spy = mocker.spy(alarm, "call") call_spy = mocker.spy(alarm, "call")
@@ -112,7 +134,7 @@ async def test_set_alarm_configure(
method: str, method: str,
value: str | int, value: str | int,
target_key: str, target_key: str,
): ) -> None:
"""Test that set_alarm_sound creates the correct call.""" """Test that set_alarm_sound creates the correct call."""
alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm))
call_spy = mocker.spy(alarm, "call") call_spy = mocker.spy(alarm, "call")

View File

@@ -26,7 +26,7 @@ autooff = parametrize(
) )
async def test_autooff_features( async def test_autooff_features(
dev: SmartDevice, feature: str, prop_name: str, type: type dev: SmartDevice, feature: str, prop_name: str, type: type
): ) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
autooff = next(get_parent_and_child_modules(dev, Module.AutoOff)) autooff = next(get_parent_and_child_modules(dev, Module.AutoOff))
assert autooff is not None assert autooff is not None
@@ -40,7 +40,7 @@ async def test_autooff_features(
@autooff @autooff
async def test_settings(dev: SmartDevice, mocker: MockerFixture): async def test_settings(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test autooff settings.""" """Test autooff settings."""
autooff = next(get_parent_and_child_modules(dev, Module.AutoOff)) autooff = next(get_parent_and_child_modules(dev, Module.AutoOff))
assert autooff assert autooff
@@ -79,7 +79,7 @@ async def test_settings(dev: SmartDevice, mocker: MockerFixture):
@pytest.mark.parametrize("is_timer_active", [True, False]) @pytest.mark.parametrize("is_timer_active", [True, False])
async def test_auto_off_at( async def test_auto_off_at(
dev: SmartDevice, mocker: MockerFixture, is_timer_active: bool dev: SmartDevice, mocker: MockerFixture, is_timer_active: bool
): ) -> None:
"""Test auto-off at sensor.""" """Test auto-off at sensor."""
autooff = next(get_parent_and_child_modules(dev, Module.AutoOff)) autooff = next(get_parent_and_child_modules(dev, Module.AutoOff))
assert autooff assert autooff

View File

@@ -1,7 +1,7 @@
import pytest import pytest
from kasa import Module from kasa import Module
from kasa.smart.modules import ChildLock from kasa.smart import SmartDevice
from ...device_fixtures import parametrize from ...device_fixtures import parametrize
@@ -19,9 +19,11 @@ childlock = parametrize(
("child_lock", "enabled", bool), ("child_lock", "enabled", bool),
], ],
) )
async def test_features(dev, feature, prop_name, type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
protect: ChildLock = dev.modules[Module.ChildLock] protect = dev.modules[Module.ChildLock]
assert protect is not None assert protect is not None
prop = getattr(protect, prop_name) prop = getattr(protect, prop_name)
@@ -33,9 +35,9 @@ async def test_features(dev, feature, prop_name, type):
@childlock @childlock
async def test_enabled(dev): async def test_enabled(dev: SmartDevice) -> None:
"""Test the API.""" """Test the API."""
protect: ChildLock = dev.modules[Module.ChildLock] protect = dev.modules[Module.ChildLock]
assert protect is not None assert protect is not None
assert isinstance(protect.enabled, bool) assert isinstance(protect.enabled, bool)

View File

@@ -1,7 +1,7 @@
import pytest import pytest
from kasa import Module from kasa import Module
from kasa.smart.modules import ChildProtection from kasa.smart import SmartDevice
from ...device_fixtures import parametrize from ...device_fixtures import parametrize
@@ -19,9 +19,11 @@ child_protection = parametrize(
("child_lock", "enabled", bool), ("child_lock", "enabled", bool),
], ],
) )
async def test_features(dev, feature, prop_name, type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
protect: ChildProtection = dev.modules[Module.ChildProtection] protect = dev.modules[Module.ChildProtection]
assert protect is not None assert protect is not None
prop = getattr(protect, prop_name) prop = getattr(protect, prop_name)
@@ -33,9 +35,9 @@ async def test_features(dev, feature, prop_name, type):
@child_protection @child_protection
async def test_enabled(dev): async def test_enabled(dev: SmartDevice) -> None:
"""Test the API.""" """Test the API."""
protect: ChildProtection = dev.modules[Module.ChildProtection] protect = dev.modules[Module.ChildProtection]
assert protect is not None assert protect is not None
assert isinstance(protect.enabled, bool) assert isinstance(protect.enabled, bool)

View File

@@ -15,7 +15,7 @@ childsetup = parametrize(
@childsetup @childsetup
async def test_childsetup_features(dev: Device): async def test_childsetup_features(dev: Device) -> None:
"""Test the exposed features.""" """Test the exposed features."""
cs = dev.modules.get(Module.ChildSetup) cs = dev.modules.get(Module.ChildSetup)
assert cs assert cs
@@ -28,7 +28,7 @@ async def test_childsetup_features(dev: Device):
@childsetup @childsetup
async def test_childsetup_pair( async def test_childsetup_pair(
dev: Device, mocker: MockerFixture, caplog: pytest.LogCaptureFixture dev: Device, mocker: MockerFixture, caplog: pytest.LogCaptureFixture
): ) -> None:
"""Test device pairing.""" """Test device pairing."""
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mock_query_helper = mocker.spy(dev, "_query_helper") mock_query_helper = mocker.spy(dev, "_query_helper")
@@ -52,7 +52,7 @@ async def test_childsetup_pair(
@childsetup @childsetup
async def test_childsetup_unpair( async def test_childsetup_unpair(
dev: Device, mocker: MockerFixture, caplog: pytest.LogCaptureFixture dev: Device, mocker: MockerFixture, caplog: pytest.LogCaptureFixture
): ) -> None:
"""Test unpair.""" """Test unpair."""
mock_query_helper = mocker.spy(dev, "_query_helper") mock_query_helper = mocker.spy(dev, "_query_helper")
DUMMY_ID = "dummy_id" DUMMY_ID = "dummy_id"

View File

@@ -25,7 +25,9 @@ clean = parametrize("clean module", component_filter="clean", protocol_filter={"
("battery_level", "battery", int), ("battery_level", "battery", int),
], ],
) )
async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
clean = next(get_parent_and_child_modules(dev, Module.Clean)) clean = next(get_parent_and_child_modules(dev, Module.Clean))
assert clean is not None assert clean is not None
@@ -94,7 +96,7 @@ async def test_actions(
value: str | int, value: str | int,
method: str, method: str,
params: dict, params: dict,
): ) -> None:
"""Test the clean actions.""" """Test the clean actions."""
clean = next(get_parent_and_child_modules(dev, Module.Clean)) clean = next(get_parent_and_child_modules(dev, Module.Clean))
call = mocker.spy(clean, "call") call = mocker.spy(clean, "call")
@@ -130,7 +132,7 @@ async def test_post_update_hook(
error: ErrorCode, error: ErrorCode,
warning_msg: str | None, warning_msg: str | None,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
): ) -> None:
"""Test that post update hook sets error states correctly.""" """Test that post update hook sets error states correctly."""
clean = next(get_parent_and_child_modules(dev, Module.Clean)) clean = next(get_parent_and_child_modules(dev, Module.Clean))
assert clean assert clean
@@ -160,7 +162,7 @@ async def test_post_update_hook(
@clean @clean
async def test_resume(dev: SmartDevice, mocker: MockerFixture): async def test_resume(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test that start calls resume if the state is paused.""" """Test that start calls resume if the state is paused."""
clean = next(get_parent_and_child_modules(dev, Module.Clean)) clean = next(get_parent_and_child_modules(dev, Module.Clean))
@@ -182,7 +184,7 @@ async def test_resume(dev: SmartDevice, mocker: MockerFixture):
@clean @clean
async def test_unknown_status( async def test_unknown_status(
dev: SmartDevice, mocker: MockerFixture, caplog: pytest.LogCaptureFixture dev: SmartDevice, mocker: MockerFixture, caplog: pytest.LogCaptureFixture
): ) -> None:
"""Test that unknown status is logged.""" """Test that unknown status is logged."""
clean = next(get_parent_and_child_modules(dev, Module.Clean)) clean = next(get_parent_and_child_modules(dev, Module.Clean))
@@ -227,7 +229,7 @@ async def test_invalid_settings(
value: str, value: str,
exc: type[Exception], exc: type[Exception],
exc_message: str, exc_message: str,
): ) -> None:
"""Test invalid settings.""" """Test invalid settings."""
clean = next(get_parent_and_child_modules(dev, Module.Clean)) clean = next(get_parent_and_child_modules(dev, Module.Clean))

View File

@@ -27,7 +27,9 @@ cleanrecords = parametrize(
("last_clean_timestamp", "last_clean_timestamp", datetime), ("last_clean_timestamp", "last_clean_timestamp", datetime),
], ],
) )
async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
records = next(get_parent_and_child_modules(dev, Module.CleanRecords)) records = next(get_parent_and_child_modules(dev, Module.CleanRecords))
assert records is not None assert records is not None
@@ -41,7 +43,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty
@cleanrecords @cleanrecords
async def test_timezone(dev: SmartDevice): async def test_timezone(dev: SmartDevice) -> None:
"""Test that timezone is added to timestamps.""" """Test that timezone is added to timestamps."""
clean_records = next(get_parent_and_child_modules(dev, Module.CleanRecords)) clean_records = next(get_parent_and_child_modules(dev, Module.CleanRecords))
assert clean_records is not None assert clean_records is not None

View File

@@ -21,7 +21,7 @@ consumables = parametrize(
"consumable_name", [consumable.id for consumable in CONSUMABLE_METAS] "consumable_name", [consumable.id for consumable in CONSUMABLE_METAS]
) )
@pytest.mark.parametrize("postfix", ["used", "remaining"]) @pytest.mark.parametrize("postfix", ["used", "remaining"])
async def test_features(dev: SmartDevice, consumable_name: str, postfix: str): async def test_features(dev: SmartDevice, consumable_name: str, postfix: str) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
consumables = next(get_parent_and_child_modules(dev, Module.Consumables)) consumables = next(get_parent_and_child_modules(dev, Module.Consumables))
assert consumables is not None assert consumables is not None
@@ -39,7 +39,7 @@ async def test_features(dev: SmartDevice, consumable_name: str, postfix: str):
) )
async def test_erase( async def test_erase(
dev: SmartDevice, mocker: MockerFixture, consumable_name: str, data_key: str dev: SmartDevice, mocker: MockerFixture, consumable_name: str, data_key: str
): ) -> None:
"""Test autocollection switch.""" """Test autocollection switch."""
consumables = next(get_parent_and_child_modules(dev, Module.Consumables)) consumables = next(get_parent_and_child_modules(dev, Module.Consumables))
call = mocker.spy(consumables, "call") call = mocker.spy(consumables, "call")

View File

@@ -16,7 +16,7 @@ contact = parametrize(
("is_open", bool), ("is_open", bool),
], ],
) )
async def test_contact_features(dev: Device, feature, type): async def test_contact_features(dev: Device, feature: str, type: type) -> None:
"""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

@@ -22,7 +22,9 @@ dustbin = parametrize(
("dustbin_mode", "mode", str), ("dustbin_mode", "mode", str),
], ],
) )
async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin))
assert dustbin is not None assert dustbin is not None
@@ -36,7 +38,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty
@dustbin @dustbin
async def test_dustbin_mode(dev: SmartDevice, mocker: MockerFixture): async def test_dustbin_mode(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test dust mode.""" """Test dust mode."""
dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin))
call = mocker.spy(dustbin, "call") call = mocker.spy(dustbin, "call")
@@ -61,7 +63,7 @@ async def test_dustbin_mode(dev: SmartDevice, mocker: MockerFixture):
@dustbin @dustbin
async def test_dustbin_mode_off(dev: SmartDevice, mocker: MockerFixture): async def test_dustbin_mode_off(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test dustbin_mode == Off.""" """Test dustbin_mode == Off."""
dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin))
call = mocker.spy(dustbin, "call") call = mocker.spy(dustbin, "call")
@@ -80,7 +82,7 @@ async def test_dustbin_mode_off(dev: SmartDevice, mocker: MockerFixture):
@dustbin @dustbin
async def test_autocollection(dev: SmartDevice, mocker: MockerFixture): async def test_autocollection(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test autocollection switch.""" """Test autocollection switch."""
dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin))
call = mocker.spy(dustbin, "call") call = mocker.spy(dustbin, "call")
@@ -101,7 +103,7 @@ async def test_autocollection(dev: SmartDevice, mocker: MockerFixture):
@dustbin @dustbin
async def test_empty_dustbin(dev: SmartDevice, mocker: MockerFixture): async def test_empty_dustbin(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test the empty dustbin feature.""" """Test the empty dustbin feature."""
dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin))
call = mocker.spy(dustbin, "call") call = mocker.spy(dustbin, "call")

View File

@@ -14,7 +14,7 @@ from tests.conftest import has_emeter_smart
@has_emeter_smart @has_emeter_smart
async def test_supported(dev: SmartDevice): async def test_supported(dev: SmartDevice) -> None:
energy_module = dev.modules.get(Module.Energy) energy_module = dev.modules.get(Module.Energy)
if not energy_module: if not energy_module:
pytest.skip(f"Energy module not supported for {dev}.") pytest.skip(f"Energy module not supported for {dev}.")
@@ -31,7 +31,7 @@ async def test_supported(dev: SmartDevice):
@has_emeter_smart @has_emeter_smart
async def test_get_energy_usage_error( async def test_get_energy_usage_error(
dev: SmartDevice, caplog: pytest.LogCaptureFixture dev: SmartDevice, caplog: pytest.LogCaptureFixture
): ) -> None:
"""Test errors on get_energy_usage.""" """Test errors on get_energy_usage."""
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)

View File

@@ -11,7 +11,7 @@ fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"S
@fan @fan
async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture): async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test fan speed feature.""" """Test fan speed feature."""
fan = next(get_parent_and_child_modules(dev, Module.Fan)) fan = next(get_parent_and_child_modules(dev, Module.Fan))
assert fan assert fan
@@ -35,7 +35,7 @@ async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
@fan @fan
async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture): async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test sleep mode feature.""" """Test sleep mode feature."""
fan = next(get_parent_and_child_modules(dev, Module.Fan)) fan = next(get_parent_and_child_modules(dev, Module.Fan))
assert fan assert fan
@@ -53,7 +53,7 @@ async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
@fan @fan
async def test_fan_module(dev: SmartDevice, mocker: MockerFixture): async def test_fan_module(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test fan speed on device interface.""" """Test fan speed on device interface."""
assert isinstance(dev, SmartDevice) assert isinstance(dev, SmartDevice)
fan = next(get_parent_and_child_modules(dev, Module.Fan)) fan = next(get_parent_and_child_modules(dev, Module.Fan))
@@ -89,7 +89,7 @@ async def test_fan_module(dev: SmartDevice, mocker: MockerFixture):
@fan @fan
async def test_fan_features(dev: SmartDevice, mocker: MockerFixture): async def test_fan_features(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test fan speed on device interface.""" """Test fan speed on device interface."""
assert isinstance(dev, SmartDevice) assert isinstance(dev, SmartDevice)
fan = next(get_parent_and_child_modules(dev, Module.Fan)) fan = next(get_parent_and_child_modules(dev, Module.Fan))

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import asyncio import asyncio
import logging import logging
from contextlib import nullcontext from contextlib import AbstractContextManager, nullcontext
from datetime import date from datetime import date
from typing import TypedDict from typing import TypedDict
@@ -31,9 +31,14 @@ firmware = parametrize(
], ],
) )
async def test_firmware_features( async def test_firmware_features(
dev: SmartDevice, feature, prop_name, type, required_version, mocker: MockerFixture dev: SmartDevice,
): feature: str,
"""Test light effect.""" prop_name: str,
type: type,
required_version: int,
mocker: MockerFixture,
) -> None:
"""Test firmware features."""
fw = dev.modules.get(Module.Firmware) fw = dev.modules.get(Module.Firmware)
assert fw assert fw
assert fw.firmware_update_info is None assert fw.firmware_update_info is None
@@ -54,7 +59,7 @@ async def test_firmware_features(
@firmware @firmware
async def test_firmware_update_info(dev: SmartDevice): async def test_firmware_update_info(dev: SmartDevice) -> None:
"""Test that the firmware UpdateInfo object deserializes correctly.""" """Test that the firmware UpdateInfo object deserializes correctly."""
fw = dev.modules.get(Module.Firmware) fw = dev.modules.get(Module.Firmware)
assert fw assert fw
@@ -68,7 +73,7 @@ async def test_firmware_update_info(dev: SmartDevice):
@firmware @firmware
async def test_update_available_without_cloud(dev: SmartDevice): async def test_update_available_without_cloud(dev: SmartDevice) -> None:
"""Test that update_available returns None when disconnected.""" """Test that update_available returns None when disconnected."""
fw = dev.modules.get(Module.Firmware) fw = dev.modules.get(Module.Firmware)
assert fw assert fw
@@ -95,9 +100,9 @@ async def test_firmware_update(
dev: SmartDevice, dev: SmartDevice,
mocker: MockerFixture, mocker: MockerFixture,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
update_available, update_available: bool,
expected_result, expected_result: AbstractContextManager,
): ) -> None:
"""Test updating firmware.""" """Test updating firmware."""
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)

View File

@@ -9,7 +9,7 @@ homekit = parametrize(
@homekit @homekit
async def test_info(dev: SmartDevice): async def test_info(dev: SmartDevice) -> None:
"""Test homekit info.""" """Test homekit info."""
homekit = dev.modules.get(Module.HomeKit) homekit = dev.modules.get(Module.HomeKit)
assert homekit assert homekit

View File

@@ -1,6 +1,7 @@
import pytest import pytest
from kasa.smart.modules import HumiditySensor from kasa import Module
from kasa.smart import SmartDevice
from ...device_fixtures import parametrize from ...device_fixtures import parametrize
@@ -17,9 +18,9 @@ humidity = parametrize(
("humidity_warning", bool), ("humidity_warning", bool),
], ],
) )
async def test_humidity_features(dev, feature, type): async def test_humidity_features(dev: SmartDevice, feature: str, type: type) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
humidity: HumiditySensor = dev.modules["HumiditySensor"] humidity = dev.modules[Module.HumiditySensor]
prop = getattr(humidity, feature) prop = getattr(humidity, feature)
assert isinstance(prop, type) assert isinstance(prop, type)

View File

@@ -16,7 +16,7 @@ light_effect = parametrize(
@light_effect @light_effect
async def test_light_effect(dev: Device, mocker: MockerFixture): async def test_light_effect(dev: Device, mocker: MockerFixture) -> None:
"""Test light effect.""" """Test light effect."""
light_effect = dev.modules.get(Module.LightEffect) light_effect = dev.modules.get(Module.LightEffect)
assert isinstance(light_effect, LightEffect) assert isinstance(light_effect, LightEffect)
@@ -46,7 +46,7 @@ async def test_light_effect(dev: Device, mocker: MockerFixture):
@pytest.mark.parametrize("effect_active", [True, False]) @pytest.mark.parametrize("effect_active", [True, False])
async def test_light_effect_brightness( async def test_light_effect_brightness(
dev: Device, effect_active: bool, mocker: MockerFixture dev: Device, effect_active: bool, mocker: MockerFixture
): ) -> None:
"""Test that light module uses light_effect for brightness when active.""" """Test that light module uses light_effect for brightness when active."""
light_module = dev.modules[Module.Light] light_module = dev.modules[Module.Light]

View File

@@ -18,7 +18,7 @@ light_strip_effect = parametrize(
@light_strip_effect @light_strip_effect
async def test_light_strip_effect(dev: Device, mocker: MockerFixture): async def test_light_strip_effect(dev: Device, mocker: MockerFixture) -> None:
"""Test light strip effect.""" """Test light strip effect."""
light_effect = dev.modules.get(Module.LightEffect) light_effect = dev.modules.get(Module.LightEffect)
@@ -63,7 +63,7 @@ async def test_light_strip_effect(dev: Device, mocker: MockerFixture):
@pytest.mark.parametrize("effect_active", [True, False]) @pytest.mark.parametrize("effect_active", [True, False])
async def test_light_effect_brightness( async def test_light_effect_brightness(
dev: Device, effect_active: bool, mocker: MockerFixture dev: Device, effect_active: bool, mocker: MockerFixture
): ) -> None:
"""Test that light module uses light_effect for brightness when active.""" """Test that light module uses light_effect for brightness when active."""
light_module = dev.modules[Module.Light] light_module = dev.modules[Module.Light]

View File

@@ -23,7 +23,7 @@ light_transition_gt_v1 = parametrize(
@light_transition_v1 @light_transition_v1
async def test_module_v1(dev: SmartDevice, mocker: MockerFixture): async def test_module_v1(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test light transition module.""" """Test light transition module."""
assert isinstance(dev, SmartDevice) assert isinstance(dev, SmartDevice)
light_transition = next(get_parent_and_child_modules(dev, Module.LightTransition)) light_transition = next(get_parent_and_child_modules(dev, Module.LightTransition))
@@ -42,7 +42,7 @@ async def test_module_v1(dev: SmartDevice, mocker: MockerFixture):
@light_transition_gt_v1 @light_transition_gt_v1
async def test_module_gt_v1(dev: SmartDevice, mocker: MockerFixture): async def test_module_gt_v1(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test light transition module.""" """Test light transition module."""
assert isinstance(dev, SmartDevice) assert isinstance(dev, SmartDevice)
light_transition = next(get_parent_and_child_modules(dev, Module.LightTransition)) light_transition = next(get_parent_and_child_modules(dev, Module.LightTransition))

View File

@@ -9,7 +9,7 @@ matter = parametrize(
@matter @matter
async def test_info(dev: SmartDevice): async def test_info(dev: SmartDevice) -> None:
"""Test matter info.""" """Test matter info."""
matter = dev.modules.get(Module.Matter) matter = dev.modules.get(Module.Matter)
assert matter assert matter

View File

@@ -20,7 +20,9 @@ mop = parametrize("has mop", component_filter="mop", protocol_filter={"SMART"})
("mop_waterlevel", "waterlevel", str), ("mop_waterlevel", "waterlevel", str),
], ],
) )
async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
mod = next(get_parent_and_child_modules(dev, Module.Mop)) mod = next(get_parent_and_child_modules(dev, Module.Mop))
assert mod is not None assert mod is not None
@@ -34,7 +36,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty
@mop @mop
async def test_mop_waterlevel(dev: SmartDevice, mocker: MockerFixture): async def test_mop_waterlevel(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test dust mode.""" """Test dust mode."""
mop_module = next(get_parent_and_child_modules(dev, Module.Mop)) mop_module = next(get_parent_and_child_modules(dev, Module.Mop))
call = mocker.spy(mop_module, "call") call = mocker.spy(mop_module, "call")

View File

@@ -16,7 +16,7 @@ motion = parametrize(
("motion_detected", bool), ("motion_detected", bool),
], ],
) )
async def test_motion_features(dev: Device, feature, type): async def test_motion_features(dev: Device, feature: str, type: type) -> None:
"""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

@@ -2,6 +2,7 @@ import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from kasa import Device, Module from kasa import Device, Module
from kasa.smart import SmartDevice
from ...device_fixtures import get_parent_and_child_modules, parametrize from ...device_fixtures import get_parent_and_child_modules, parametrize
@@ -20,7 +21,9 @@ powerprotection = parametrize(
("power_protection_threshold", "protection_threshold", int), ("power_protection_threshold", "protection_threshold", int),
], ],
) )
async def test_features(dev, feature, prop_name, type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection)) powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection))
assert powerprot assert powerprot
@@ -35,7 +38,7 @@ async def test_features(dev, feature, prop_name, type):
@powerprotection @powerprotection
async def test_set_enable(dev: Device, mocker: MockerFixture): async def test_set_enable(dev: Device, mocker: MockerFixture) -> None:
"""Test enable.""" """Test enable."""
powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection)) powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection))
assert powerprot assert powerprot
@@ -88,7 +91,7 @@ async def test_set_enable(dev: Device, mocker: MockerFixture):
@powerprotection @powerprotection
async def test_set_threshold(dev: Device, mocker: MockerFixture): async def test_set_threshold(dev: Device, mocker: MockerFixture) -> None:
"""Test enable.""" """Test enable."""
powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection)) powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection))
assert powerprot assert powerprot

View File

@@ -20,7 +20,9 @@ speaker = parametrize(
("volume", "volume", int), ("volume", "volume", int),
], ],
) )
async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: type): async def test_features(
dev: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
speaker = next(get_parent_and_child_modules(dev, Module.Speaker)) speaker = next(get_parent_and_child_modules(dev, Module.Speaker))
assert speaker is not None assert speaker is not None
@@ -34,7 +36,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty
@speaker @speaker
async def test_set_volume(dev: SmartDevice, mocker: MockerFixture): async def test_set_volume(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test speaker settings.""" """Test speaker settings."""
speaker = next(get_parent_and_child_modules(dev, Module.Speaker)) speaker = next(get_parent_and_child_modules(dev, Module.Speaker))
assert speaker is not None assert speaker is not None
@@ -61,7 +63,7 @@ async def test_set_volume(dev: SmartDevice, mocker: MockerFixture):
@speaker @speaker
async def test_locate(dev: SmartDevice, mocker: MockerFixture): async def test_locate(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test the locate method.""" """Test the locate method."""
speaker = next(get_parent_and_child_modules(dev, Module.Speaker)) speaker = next(get_parent_and_child_modules(dev, Module.Speaker))
call = mocker.spy(speaker, "call") call = mocker.spy(speaker, "call")

View File

@@ -1,6 +1,7 @@
import pytest import pytest
from kasa.smart.modules import TemperatureSensor from kasa import Module
from kasa.smart import SmartDevice
from ...device_fixtures import parametrize from ...device_fixtures import parametrize
@@ -23,9 +24,9 @@ temperature_warning = parametrize(
("temperature_unit", str), ("temperature_unit", str),
], ],
) )
async def test_temperature_features(dev, feature, type): async def test_temperature_features(dev: SmartDevice, feature: str, type: type) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
temp_module: TemperatureSensor = dev.modules["TemperatureSensor"] temp_module = dev.modules[Module.TemperatureSensor]
prop = getattr(temp_module, feature) prop = getattr(temp_module, feature)
assert isinstance(prop, type) assert isinstance(prop, type)
@@ -36,9 +37,9 @@ async def test_temperature_features(dev, feature, type):
@temperature_warning @temperature_warning
async def test_temperature_warning(dev): async def test_temperature_warning(dev: SmartDevice) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
temp_module: TemperatureSensor = dev.modules["TemperatureSensor"] temp_module = dev.modules[Module.TemperatureSensor]
assert hasattr(temp_module, "temperature_warning") assert hasattr(temp_module, "temperature_warning")
assert isinstance(temp_module.temperature_warning, bool) assert isinstance(temp_module.temperature_warning, bool)

View File

@@ -3,7 +3,8 @@ import re
import pytest import pytest
from kasa.smart.modules import TemperatureControl from kasa import Module
from kasa.smart import SmartDevice
from kasa.smart.modules.temperaturecontrol import ThermostatState from kasa.smart.modules.temperaturecontrol import ThermostatState
from ...device_fixtures import parametrize, thermostats_smart from ...device_fixtures import parametrize, thermostats_smart
@@ -23,9 +24,11 @@ temperature = parametrize(
("temperature_offset", int), ("temperature_offset", int),
], ],
) )
async def test_temperature_control_features(dev, feature, type): async def test_temperature_control_features(
dev: SmartDevice, feature: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module = dev.modules[Module.TemperatureControl]
prop = getattr(temp_module, feature) prop = getattr(temp_module, feature)
assert isinstance(prop, type) assert isinstance(prop, type)
@@ -40,9 +43,9 @@ async def test_temperature_control_features(dev, feature, type):
@thermostats_smart @thermostats_smart
async def test_set_temperature_turns_heating_on(dev): async def test_set_temperature_turns_heating_on(dev: SmartDevice) -> None:
"""Test that set_temperature turns heating on.""" """Test that set_temperature turns heating on."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module = dev.modules[Module.TemperatureControl]
await temp_module.set_state(False) await temp_module.set_state(False)
await dev.update() await dev.update()
@@ -57,9 +60,9 @@ async def test_set_temperature_turns_heating_on(dev):
@thermostats_smart @thermostats_smart
async def test_set_temperature_invalid_values(dev): async def test_set_temperature_invalid_values(dev: SmartDevice) -> None:
"""Test that out-of-bounds temperature values raise errors.""" """Test that out-of-bounds temperature values raise errors."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module = dev.modules[Module.TemperatureControl]
with pytest.raises( with pytest.raises(
ValueError, match="Invalid target temperature -1, must be in range" ValueError, match="Invalid target temperature -1, must be in range"
@@ -73,9 +76,9 @@ async def test_set_temperature_invalid_values(dev):
@thermostats_smart @thermostats_smart
async def test_temperature_offset(dev): async def test_temperature_offset(dev: SmartDevice) -> None:
"""Test the temperature offset API.""" """Test the temperature offset API."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module = dev.modules[Module.TemperatureControl]
with pytest.raises( with pytest.raises(
ValueError, match=re.escape("Temperature offset must be [-10, 10]") ValueError, match=re.escape("Temperature offset must be [-10, 10]")
): ):
@@ -111,9 +114,11 @@ async def test_temperature_offset(dev):
pytest.param(ThermostatState.Unknown, ["invalid"], False, id="unknown state"), pytest.param(ThermostatState.Unknown, ["invalid"], False, id="unknown state"),
], ],
) )
async def test_thermostat_mode(dev, mode, states, frost_protection): async def test_thermostat_mode(
dev: SmartDevice, mode: ThermostatState, states: list[str], frost_protection: bool
) -> None:
"""Test different thermostat modes.""" """Test different thermostat modes."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module = dev.modules[Module.TemperatureControl]
temp_module.data["frost_protection_on"] = frost_protection temp_module.data["frost_protection_on"] = frost_protection
temp_module.data["trv_states"] = states temp_module.data["trv_states"] = states
@@ -138,9 +143,15 @@ async def test_thermostat_mode(dev, mode, states, frost_protection):
], ],
) )
@pytest.mark.xdist_group(name="caplog") @pytest.mark.xdist_group(name="caplog")
async def test_thermostat_mode_warnings(dev, mode, states, msg, caplog): async def test_thermostat_mode_warnings(
dev: SmartDevice,
mode: ThermostatState,
states: list[str],
msg: str,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test thermostat modes that should log a warning.""" """Test thermostat modes that should log a warning."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module = dev.modules[Module.TemperatureControl]
caplog.set_level(logging.WARNING) caplog.set_level(logging.WARNING)
temp_module.data["trv_states"] = states temp_module.data["trv_states"] = states
@@ -149,17 +160,19 @@ async def test_thermostat_mode_warnings(dev, mode, states, msg, caplog):
@thermostats_smart @thermostats_smart
async def test_thermostat_heating_with_low_battery(dev): async def test_thermostat_heating_with_low_battery(dev: SmartDevice) -> None:
"""Test that mode is reported correctly with extra states.""" """Test that mode is reported correctly with extra states."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module = dev.modules[Module.TemperatureControl]
temp_module.data["trv_states"] = ["low_battery", "heating"] temp_module.data["trv_states"] = ["low_battery", "heating"]
assert temp_module.mode is ThermostatState.Heating assert temp_module.mode is ThermostatState.Heating
@thermostats_smart @thermostats_smart
async def test_thermostat_idle_with_low_battery(dev, caplog): async def test_thermostat_idle_with_low_battery(
dev: SmartDevice, caplog: pytest.LogCaptureFixture
) -> None:
"""Test that mode is reported correctly with extra states.""" """Test that mode is reported correctly with extra states."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module = dev.modules[Module.TemperatureControl]
temp_module.data["trv_states"] = ["low_battery"] temp_module.data["trv_states"] = ["low_battery"]
with caplog.at_level(logging.WARNING): with caplog.at_level(logging.WARNING):
assert temp_module.mode is ThermostatState.Idle assert temp_module.mode is ThermostatState.Idle

View File

@@ -10,7 +10,7 @@ triggerlogs = parametrize(
@triggerlogs @triggerlogs
async def test_trigger_logs(dev: Device): async def test_trigger_logs(dev: Device) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
triggerlogs = dev.modules.get(Module.TriggerLogs) triggerlogs = dev.modules.get(Module.TriggerLogs)
assert triggerlogs is not None assert triggerlogs is not None

View File

@@ -3,7 +3,8 @@ from enum import Enum
import pytest import pytest
from kasa.smart.modules import WaterleakSensor from kasa import Module
from kasa.smart import SmartDevice
from ...conftest import get_device_for_fixture_protocol from ...conftest import get_device_for_fixture_protocol
from ...device_fixtures import parametrize from ...device_fixtures import parametrize
@@ -28,10 +29,12 @@ async def parent(request):
("water_leak", "status", Enum), ("water_leak", "status", Enum),
], ],
) )
async def test_waterleak_properties(dev, parent, feature, prop_name, type): async def test_waterleak_properties(
dev: SmartDevice, parent: SmartDevice, feature: str, prop_name: str, type: type
) -> None:
"""Test that features are registered and work as expected.""" """Test that features are registered and work as expected."""
dev._parent = parent dev._parent = parent
waterleak: WaterleakSensor = dev.modules["WaterleakSensor"] waterleak = dev.modules[Module.WaterleakSensor]
prop = getattr(waterleak, prop_name) prop = getattr(waterleak, prop_name)
assert isinstance(prop, type) assert isinstance(prop, type)
@@ -42,10 +45,10 @@ async def test_waterleak_properties(dev, parent, feature, prop_name, type):
@waterleak @waterleak
async def test_waterleak_features(dev, parent): async def test_waterleak_features(dev: SmartDevice, parent: SmartDevice) -> None:
"""Test waterleak features.""" """Test waterleak features."""
dev._parent = parent dev._parent = parent
waterleak: WaterleakSensor = dev.modules["WaterleakSensor"] waterleak = dev.modules[Module.WaterleakSensor]
assert "water_leak" in dev.features assert "water_leak" in dev.features
assert dev.features["water_leak"].value == waterleak.status assert dev.features["water_leak"].value == waterleak.status

View File

@@ -44,7 +44,9 @@ hub_all = parametrize_combine([hubs_smart, hub_smartcam])
@device_smart @device_smart
@pytest.mark.requires_dummy @pytest.mark.requires_dummy
@pytest.mark.xdist_group(name="caplog") @pytest.mark.xdist_group(name="caplog")
async def test_try_get_response(dev: SmartDevice, caplog): async def test_try_get_response(
dev: SmartDevice, caplog: pytest.LogCaptureFixture
) -> None:
mock_response: dict = { mock_response: dict = {
"get_device_info": SmartErrorCode.PARAMS_ERROR, "get_device_info": SmartErrorCode.PARAMS_ERROR,
} }
@@ -56,7 +58,7 @@ async def test_try_get_response(dev: SmartDevice, caplog):
@device_smart @device_smart
@pytest.mark.requires_dummy @pytest.mark.requires_dummy
async def test_update_no_device_info(dev: SmartDevice, mocker: MockerFixture): async def test_update_no_device_info(dev: SmartDevice, mocker: MockerFixture) -> None:
mock_response: dict = { mock_response: dict = {
"get_device_usage": {}, "get_device_usage": {},
"get_device_time": {}, "get_device_time": {},
@@ -68,7 +70,9 @@ async def test_update_no_device_info(dev: SmartDevice, mocker: MockerFixture):
@smart_discovery @smart_discovery
async def test_device_type_no_update(discovery_mock, caplog: pytest.LogCaptureFixture): async def test_device_type_no_update(
discovery_mock, caplog: pytest.LogCaptureFixture
) -> None:
"""Test device type and repr when device not updated.""" """Test device type and repr when device not updated."""
dev = SmartDevice(DISCOVERY_MOCK_IP) dev = SmartDevice(DISCOVERY_MOCK_IP)
assert dev.device_type is DeviceType.Unknown assert dev.device_type is DeviceType.Unknown
@@ -96,7 +100,7 @@ async def test_device_type_no_update(discovery_mock, caplog: pytest.LogCaptureFi
@device_smart @device_smart
async def test_initial_update(dev: SmartDevice, mocker: MockerFixture): async def test_initial_update(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test the initial update cycle.""" """Test the initial update cycle."""
# As the fixture data is already initialized, we reset the state for testing # As the fixture data is already initialized, we reset the state for testing
dev._components_raw = None dev._components_raw = None
@@ -124,7 +128,7 @@ async def test_initial_update(dev: SmartDevice, mocker: MockerFixture):
@device_smart @device_smart
async def test_negotiate(dev: SmartDevice, mocker: MockerFixture): async def test_negotiate(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test that the initial negotiation performs expected steps.""" """Test that the initial negotiation performs expected steps."""
# As the fixture data is already initialized, we reset the state for testing # As the fixture data is already initialized, we reset the state for testing
dev._components_raw = None dev._components_raw = None
@@ -158,7 +162,7 @@ async def test_negotiate(dev: SmartDevice, mocker: MockerFixture):
@device_smart @device_smart
async def test_update_module_queries(dev: SmartDevice, mocker: MockerFixture): async def test_update_module_queries(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Test that the regular update uses queries from all supported modules.""" """Test that the regular update uses queries from all supported modules."""
# We need to have some modules initialized by now # We need to have some modules initialized by now
assert dev._modules assert dev._modules
@@ -195,7 +199,7 @@ async def test_update_module_update_delays(
mocker: MockerFixture, mocker: MockerFixture,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
): ) -> None:
"""Test that modules with minimum delays delay.""" """Test that modules with minimum delays delay."""
# We need to have some modules initialized by now # We need to have some modules initialized by now
assert dev._modules assert dev._modules
@@ -253,7 +257,7 @@ async def test_hub_children_update_delays(
mocker: MockerFixture, mocker: MockerFixture,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
): ) -> None:
"""Test that hub children use the correct delay.""" """Test that hub children use the correct delay."""
if not dev.children: if not dev.children:
pytest.skip(f"Device {dev.model} does not have children.") pytest.skip(f"Device {dev.model} does not have children.")
@@ -416,14 +420,15 @@ async def test_update_module_query_errors(
mocker: MockerFixture, mocker: MockerFixture,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
first_update, first_update: bool,
error_type, error_type: SmartErrorCode | TimeoutError,
recover, recover: bool,
): ) -> None:
"""Test that modules that disabled / removed on query failures. """Test that modules that disabled / removed on query failures.
i.e. the whole query times out rather than device returns an error. i.e. the whole query times out rather than device returns an error.
""" """
caplog.set_level(logging.DEBUG)
# We need to have some modules initialized by now # We need to have some modules initialized by now
assert dev._modules assert dev._modules
@@ -480,8 +485,6 @@ async def test_update_module_query_errors(
raise TimeoutError() raise TimeoutError()
if len(child_requests) > 1: if len(child_requests) > 1:
raise TimeoutError() raise TimeoutError()
if raise_error:
resp = { resp = {
"method": child_requests[0]["method"], "method": child_requests[0]["method"],
"error_code": error_type.value, "error_code": error_type.value,
@@ -594,7 +597,7 @@ async def test_update_module_query_errors(
assert emod.status is not None assert emod.status is not None
async def test_get_modules(): async def test_get_modules() -> None:
"""Test getting modules for child and parent modules.""" """Test getting modules for child and parent modules."""
dummy_device = await get_device_for_fixture_protocol( dummy_device = await get_device_for_fixture_protocol(
"KS240(US)_1.0_1.0.5.json", "SMART" "KS240(US)_1.0_1.0.5.json", "SMART"
@@ -629,7 +632,9 @@ async def test_get_modules():
@device_smart @device_smart
async def test_smartdevice_cloud_connection(dev: SmartDevice, mocker: MockerFixture): async def test_smartdevice_cloud_connection(
dev: SmartDevice, mocker: MockerFixture
) -> None:
"""Test is_cloud_connected property.""" """Test is_cloud_connected property."""
assert isinstance(dev, SmartDevice) assert isinstance(dev, SmartDevice)
assert "cloud_connect" in dev._components assert "cloud_connect" in dev._components
@@ -697,7 +702,7 @@ async def test_smartdevice_cloud_connection(dev: SmartDevice, mocker: MockerFixt
@variable_temp_smart @variable_temp_smart
async def test_smart_temp_range(dev: Device): async def test_smart_temp_range(dev: Device) -> None:
light = dev.modules.get(Module.Light) light = dev.modules.get(Module.Light)
assert light assert light
color_temp_feat = light.get_feature("color_temp") color_temp_feat = light.get_feature("color_temp")
@@ -708,7 +713,7 @@ async def test_smart_temp_range(dev: Device):
@device_smart @device_smart
async def test_initialize_modules_sysinfo_lookup_keys( async def test_initialize_modules_sysinfo_lookup_keys(
dev: SmartDevice, mocker: MockerFixture dev: SmartDevice, mocker: MockerFixture
): ) -> None:
"""Test that matching modules using SYSINFO_LOOKUP_KEYS are initialized correctly.""" """Test that matching modules using SYSINFO_LOOKUP_KEYS are initialized correctly."""
class AvailableKey(SmartModule): class AvailableKey(SmartModule):
@@ -737,7 +742,7 @@ async def test_initialize_modules_sysinfo_lookup_keys(
@device_smart @device_smart
async def test_initialize_modules_required_component( async def test_initialize_modules_required_component(
dev: SmartDevice, mocker: MockerFixture dev: SmartDevice, mocker: MockerFixture
): ) -> None:
"""Test that matching modules using REQUIRED_COMPONENT are initialized correctly.""" """Test that matching modules using REQUIRED_COMPONENT are initialized correctly."""
class AvailableComponent(SmartModule): class AvailableComponent(SmartModule):
@@ -763,7 +768,7 @@ async def test_initialize_modules_required_component(
assert "NonExistingComponent" not in dev.modules assert "NonExistingComponent" not in dev.modules
async def test_smartmodule_query(): async def test_smartmodule_query() -> None:
"""Test that a module that doesn't set QUERY_GETTER_NAME has empty query.""" """Test that a module that doesn't set QUERY_GETTER_NAME has empty query."""
class DummyModule(SmartModule): class DummyModule(SmartModule):
@@ -779,7 +784,7 @@ async def test_smartmodule_query():
@hub_all @hub_all
@pytest.mark.xdist_group(name="caplog") @pytest.mark.xdist_group(name="caplog")
@pytest.mark.requires_dummy @pytest.mark.requires_dummy
async def test_dynamic_devices(dev: Device, caplog: pytest.LogCaptureFixture): async def test_dynamic_devices(dev: Device, caplog: pytest.LogCaptureFixture) -> None:
"""Test dynamic child devices.""" """Test dynamic child devices."""
if not dev.children: if not dev.children:
pytest.skip(f"Device {dev.model} does not have children.") pytest.skip(f"Device {dev.model} does not have children.")
@@ -992,7 +997,7 @@ async def test_dynamic_devices(dev: Device, caplog: pytest.LogCaptureFixture):
@hubs_smart @hubs_smart
async def test_unpair(dev: SmartDevice, mocker: MockerFixture): async def test_unpair(dev: SmartDevice, mocker: MockerFixture) -> None:
"""Verify that unpair calls childsetup module.""" """Verify that unpair calls childsetup module."""
if not dev.children: if not dev.children:
pytest.skip("device has no children") pytest.skip("device has no children")