From 88e1c27bd05f6c6f64ffb20da03c6c2ef7496222 Mon Sep 17 00:00:00 2001 From: ZeliardM <140266236+ZeliardM@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:11:59 -0400 Subject: [PATCH] tests: add type annotations to Smart tests (#1686) Add type annotations across all 'smart' tests, enabling mypy to check test function bodies, catching type errors that were previously hidden. --- tests/smart/features/test_brightness.py | 4 +- tests/smart/features/test_colortemp.py | 2 +- tests/smart/modules/test_alarm.py | 32 ++++++++++-- tests/smart/modules/test_autooff.py | 6 +-- tests/smart/modules/test_childlock.py | 12 +++-- tests/smart/modules/test_childprotection.py | 12 +++-- tests/smart/modules/test_childsetup.py | 6 +-- tests/smart/modules/test_clean.py | 14 +++--- tests/smart/modules/test_cleanrecords.py | 6 ++- tests/smart/modules/test_consumables.py | 4 +- tests/smart/modules/test_contact.py | 2 +- tests/smart/modules/test_dustbin.py | 12 +++-- tests/smart/modules/test_energy.py | 4 +- tests/smart/modules/test_fan.py | 8 +-- tests/smart/modules/test_firmware.py | 23 +++++---- tests/smart/modules/test_homekit.py | 2 +- tests/smart/modules/test_humidity.py | 7 +-- tests/smart/modules/test_light_effect.py | 4 +- .../smart/modules/test_light_strip_effect.py | 4 +- tests/smart/modules/test_lighttransition.py | 4 +- tests/smart/modules/test_matter.py | 2 +- tests/smart/modules/test_mop.py | 6 ++- tests/smart/modules/test_motionsensor.py | 2 +- tests/smart/modules/test_powerprotection.py | 9 ++-- tests/smart/modules/test_speaker.py | 8 +-- tests/smart/modules/test_temperature.py | 11 +++-- .../smart/modules/test_temperaturecontrol.py | 47 +++++++++++------- tests/smart/modules/test_triggerlogs.py | 2 +- tests/smart/modules/test_waterleak.py | 13 +++-- tests/smart/test_smartdevice.py | 49 ++++++++++--------- 30 files changed, 192 insertions(+), 125 deletions(-) diff --git a/tests/smart/features/test_brightness.py b/tests/smart/features/test_brightness.py index ff38854a..d0db11aa 100644 --- a/tests/smart/features/test_brightness.py +++ b/tests/smart/features/test_brightness.py @@ -9,7 +9,7 @@ brightness = parametrize("brightness smart", component_filter="brightness") @brightness -async def test_brightness_component(dev: SmartDevice): +async def test_brightness_component(dev: SmartDevice) -> None: """Test brightness feature.""" brightness = next(get_parent_and_child_modules(dev, "Brightness")) assert brightness @@ -35,7 +35,7 @@ async def test_brightness_component(dev: SmartDevice): @dimmable_iot -async def test_brightness_dimmable(dev: IotDevice): +async def test_brightness_dimmable(dev: IotDevice) -> None: """Test brightness feature.""" assert isinstance(dev, IotDevice) assert "brightness" in dev.sys_info or bool(dev.sys_info["is_dimmable"]) diff --git a/tests/smart/features/test_colortemp.py b/tests/smart/features/test_colortemp.py index 055c5b29..4831c1ce 100644 --- a/tests/smart/features/test_colortemp.py +++ b/tests/smart/features/test_colortemp.py @@ -6,7 +6,7 @@ from ...conftest import variable_temp_smart @variable_temp_smart -async def test_colortemp_component(dev: SmartDevice): +async def test_colortemp_component(dev: SmartDevice) -> None: """Test brightness feature.""" assert isinstance(dev, SmartDevice) assert "color_temperature" in dev._components diff --git a/tests/smart/modules/test_alarm.py b/tests/smart/modules/test_alarm.py index 25d24a58..3af80e99 100644 --- a/tests/smart/modules/test_alarm.py +++ b/tests/smart/modules/test_alarm.py @@ -1,17 +1,32 @@ from __future__ import annotations +from typing import TypedDict + import pytest from pytest_mock import MockerFixture from kasa import Module from kasa.smart import SmartDevice from kasa.smart.modules import Alarm +from kasa.smart.modules.alarm import AlarmVolume from ...device_fixtures import get_parent_and_child_modules, parametrize 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 @pytest.mark.parametrize( ("feature", "prop_name", "type"), @@ -23,7 +38,9 @@ alarm = parametrize("has alarm", component_filter="alarm", protocol_filter={"SMA ("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.""" alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) assert alarm is not None @@ -37,7 +54,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty @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.""" alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) 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.""" alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) call_spy = mocker.spy(alarm, "call") @@ -86,7 +108,7 @@ async def test_play(dev: SmartDevice, kwargs, request_params, mocker: MockerFixt @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.""" alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) call_spy = mocker.spy(alarm, "call") @@ -112,7 +134,7 @@ async def test_set_alarm_configure( method: str, value: str | int, target_key: str, -): +) -> None: """Test that set_alarm_sound creates the correct call.""" alarm: Alarm = next(get_parent_and_child_modules(dev, Module.Alarm)) call_spy = mocker.spy(alarm, "call") diff --git a/tests/smart/modules/test_autooff.py b/tests/smart/modules/test_autooff.py index 9bdf9e56..cb804830 100644 --- a/tests/smart/modules/test_autooff.py +++ b/tests/smart/modules/test_autooff.py @@ -26,7 +26,7 @@ autooff = parametrize( ) async def test_autooff_features( dev: SmartDevice, feature: str, prop_name: str, type: type -): +) -> None: """Test that features are registered and work as expected.""" autooff = next(get_parent_and_child_modules(dev, Module.AutoOff)) assert autooff is not None @@ -40,7 +40,7 @@ async def test_autooff_features( @autooff -async def test_settings(dev: SmartDevice, mocker: MockerFixture): +async def test_settings(dev: SmartDevice, mocker: MockerFixture) -> None: """Test autooff settings.""" autooff = next(get_parent_and_child_modules(dev, Module.AutoOff)) assert autooff @@ -79,7 +79,7 @@ async def test_settings(dev: SmartDevice, mocker: MockerFixture): @pytest.mark.parametrize("is_timer_active", [True, False]) async def test_auto_off_at( dev: SmartDevice, mocker: MockerFixture, is_timer_active: bool -): +) -> None: """Test auto-off at sensor.""" autooff = next(get_parent_and_child_modules(dev, Module.AutoOff)) assert autooff diff --git a/tests/smart/modules/test_childlock.py b/tests/smart/modules/test_childlock.py index 2ffa9104..a312d5f4 100644 --- a/tests/smart/modules/test_childlock.py +++ b/tests/smart/modules/test_childlock.py @@ -1,7 +1,7 @@ import pytest from kasa import Module -from kasa.smart.modules import ChildLock +from kasa.smart import SmartDevice from ...device_fixtures import parametrize @@ -19,9 +19,11 @@ childlock = parametrize( ("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.""" - protect: ChildLock = dev.modules[Module.ChildLock] + protect = dev.modules[Module.ChildLock] assert protect is not None prop = getattr(protect, prop_name) @@ -33,9 +35,9 @@ async def test_features(dev, feature, prop_name, type): @childlock -async def test_enabled(dev): +async def test_enabled(dev: SmartDevice) -> None: """Test the API.""" - protect: ChildLock = dev.modules[Module.ChildLock] + protect = dev.modules[Module.ChildLock] assert protect is not None assert isinstance(protect.enabled, bool) diff --git a/tests/smart/modules/test_childprotection.py b/tests/smart/modules/test_childprotection.py index ad2878e5..e00cbb45 100644 --- a/tests/smart/modules/test_childprotection.py +++ b/tests/smart/modules/test_childprotection.py @@ -1,7 +1,7 @@ import pytest from kasa import Module -from kasa.smart.modules import ChildProtection +from kasa.smart import SmartDevice from ...device_fixtures import parametrize @@ -19,9 +19,11 @@ child_protection = parametrize( ("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.""" - protect: ChildProtection = dev.modules[Module.ChildProtection] + protect = dev.modules[Module.ChildProtection] assert protect is not None prop = getattr(protect, prop_name) @@ -33,9 +35,9 @@ async def test_features(dev, feature, prop_name, type): @child_protection -async def test_enabled(dev): +async def test_enabled(dev: SmartDevice) -> None: """Test the API.""" - protect: ChildProtection = dev.modules[Module.ChildProtection] + protect = dev.modules[Module.ChildProtection] assert protect is not None assert isinstance(protect.enabled, bool) diff --git a/tests/smart/modules/test_childsetup.py b/tests/smart/modules/test_childsetup.py index afe36b0c..749fcc57 100644 --- a/tests/smart/modules/test_childsetup.py +++ b/tests/smart/modules/test_childsetup.py @@ -15,7 +15,7 @@ childsetup = parametrize( @childsetup -async def test_childsetup_features(dev: Device): +async def test_childsetup_features(dev: Device) -> None: """Test the exposed features.""" cs = dev.modules.get(Module.ChildSetup) assert cs @@ -28,7 +28,7 @@ async def test_childsetup_features(dev: Device): @childsetup async def test_childsetup_pair( dev: Device, mocker: MockerFixture, caplog: pytest.LogCaptureFixture -): +) -> None: """Test device pairing.""" caplog.set_level(logging.INFO) mock_query_helper = mocker.spy(dev, "_query_helper") @@ -52,7 +52,7 @@ async def test_childsetup_pair( @childsetup async def test_childsetup_unpair( dev: Device, mocker: MockerFixture, caplog: pytest.LogCaptureFixture -): +) -> None: """Test unpair.""" mock_query_helper = mocker.spy(dev, "_query_helper") DUMMY_ID = "dummy_id" diff --git a/tests/smart/modules/test_clean.py b/tests/smart/modules/test_clean.py index 0f935959..326e17d6 100644 --- a/tests/smart/modules/test_clean.py +++ b/tests/smart/modules/test_clean.py @@ -25,7 +25,9 @@ clean = parametrize("clean module", component_filter="clean", protocol_filter={" ("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.""" clean = next(get_parent_and_child_modules(dev, Module.Clean)) assert clean is not None @@ -94,7 +96,7 @@ async def test_actions( value: str | int, method: str, params: dict, -): +) -> None: """Test the clean actions.""" clean = next(get_parent_and_child_modules(dev, Module.Clean)) call = mocker.spy(clean, "call") @@ -130,7 +132,7 @@ async def test_post_update_hook( error: ErrorCode, warning_msg: str | None, caplog: pytest.LogCaptureFixture, -): +) -> None: """Test that post update hook sets error states correctly.""" clean = next(get_parent_and_child_modules(dev, Module.Clean)) assert clean @@ -160,7 +162,7 @@ async def test_post_update_hook( @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.""" clean = next(get_parent_and_child_modules(dev, Module.Clean)) @@ -182,7 +184,7 @@ async def test_resume(dev: SmartDevice, mocker: MockerFixture): @clean async def test_unknown_status( dev: SmartDevice, mocker: MockerFixture, caplog: pytest.LogCaptureFixture -): +) -> None: """Test that unknown status is logged.""" clean = next(get_parent_and_child_modules(dev, Module.Clean)) @@ -227,7 +229,7 @@ async def test_invalid_settings( value: str, exc: type[Exception], exc_message: str, -): +) -> None: """Test invalid settings.""" clean = next(get_parent_and_child_modules(dev, Module.Clean)) diff --git a/tests/smart/modules/test_cleanrecords.py b/tests/smart/modules/test_cleanrecords.py index cef69286..adfa1cef 100644 --- a/tests/smart/modules/test_cleanrecords.py +++ b/tests/smart/modules/test_cleanrecords.py @@ -27,7 +27,9 @@ cleanrecords = parametrize( ("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.""" records = next(get_parent_and_child_modules(dev, Module.CleanRecords)) assert records is not None @@ -41,7 +43,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty @cleanrecords -async def test_timezone(dev: SmartDevice): +async def test_timezone(dev: SmartDevice) -> None: """Test that timezone is added to timestamps.""" clean_records = next(get_parent_and_child_modules(dev, Module.CleanRecords)) assert clean_records is not None diff --git a/tests/smart/modules/test_consumables.py b/tests/smart/modules/test_consumables.py index 7a28f3be..4b61d3e0 100644 --- a/tests/smart/modules/test_consumables.py +++ b/tests/smart/modules/test_consumables.py @@ -21,7 +21,7 @@ consumables = parametrize( "consumable_name", [consumable.id for consumable in CONSUMABLE_METAS] ) @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.""" consumables = next(get_parent_and_child_modules(dev, Module.Consumables)) assert consumables is not None @@ -39,7 +39,7 @@ async def test_features(dev: SmartDevice, consumable_name: str, postfix: str): ) async def test_erase( dev: SmartDevice, mocker: MockerFixture, consumable_name: str, data_key: str -): +) -> None: """Test autocollection switch.""" consumables = next(get_parent_and_child_modules(dev, Module.Consumables)) call = mocker.spy(consumables, "call") diff --git a/tests/smart/modules/test_contact.py b/tests/smart/modules/test_contact.py index c5c4c935..384dae84 100644 --- a/tests/smart/modules/test_contact.py +++ b/tests/smart/modules/test_contact.py @@ -16,7 +16,7 @@ contact = parametrize( ("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.""" contact = dev.modules.get(Module.ContactSensor) assert contact is not None diff --git a/tests/smart/modules/test_dustbin.py b/tests/smart/modules/test_dustbin.py index ecc68b6b..5f685ced 100644 --- a/tests/smart/modules/test_dustbin.py +++ b/tests/smart/modules/test_dustbin.py @@ -22,7 +22,9 @@ dustbin = parametrize( ("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.""" dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) assert dustbin is not None @@ -36,7 +38,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty @dustbin -async def test_dustbin_mode(dev: SmartDevice, mocker: MockerFixture): +async def test_dustbin_mode(dev: SmartDevice, mocker: MockerFixture) -> None: """Test dust mode.""" dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) call = mocker.spy(dustbin, "call") @@ -61,7 +63,7 @@ async def test_dustbin_mode(dev: SmartDevice, mocker: MockerFixture): @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.""" dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) call = mocker.spy(dustbin, "call") @@ -80,7 +82,7 @@ async def test_dustbin_mode_off(dev: SmartDevice, mocker: MockerFixture): @dustbin -async def test_autocollection(dev: SmartDevice, mocker: MockerFixture): +async def test_autocollection(dev: SmartDevice, mocker: MockerFixture) -> None: """Test autocollection switch.""" dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) call = mocker.spy(dustbin, "call") @@ -101,7 +103,7 @@ async def test_autocollection(dev: SmartDevice, mocker: MockerFixture): @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.""" dustbin = next(get_parent_and_child_modules(dev, Module.Dustbin)) call = mocker.spy(dustbin, "call") diff --git a/tests/smart/modules/test_energy.py b/tests/smart/modules/test_energy.py index 7b31d74b..96da5ea8 100644 --- a/tests/smart/modules/test_energy.py +++ b/tests/smart/modules/test_energy.py @@ -14,7 +14,7 @@ from tests.conftest import 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) if not energy_module: pytest.skip(f"Energy module not supported for {dev}.") @@ -31,7 +31,7 @@ async def test_supported(dev: SmartDevice): @has_emeter_smart async def test_get_energy_usage_error( dev: SmartDevice, caplog: pytest.LogCaptureFixture -): +) -> None: """Test errors on get_energy_usage.""" caplog.set_level(logging.DEBUG) diff --git a/tests/smart/modules/test_fan.py b/tests/smart/modules/test_fan.py index 5f505e74..dc3575dc 100644 --- a/tests/smart/modules/test_fan.py +++ b/tests/smart/modules/test_fan.py @@ -11,7 +11,7 @@ fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"S @fan -async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture): +async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture) -> None: """Test fan speed feature.""" fan = next(get_parent_and_child_modules(dev, Module.Fan)) assert fan @@ -35,7 +35,7 @@ async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture): @fan -async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture): +async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture) -> None: """Test sleep mode feature.""" fan = next(get_parent_and_child_modules(dev, Module.Fan)) assert fan @@ -53,7 +53,7 @@ async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture): @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.""" assert isinstance(dev, SmartDevice) fan = next(get_parent_and_child_modules(dev, Module.Fan)) @@ -89,7 +89,7 @@ async def test_fan_module(dev: SmartDevice, mocker: MockerFixture): @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.""" assert isinstance(dev, SmartDevice) fan = next(get_parent_and_child_modules(dev, Module.Fan)) diff --git a/tests/smart/modules/test_firmware.py b/tests/smart/modules/test_firmware.py index e3fe5bb3..220c4dc2 100644 --- a/tests/smart/modules/test_firmware.py +++ b/tests/smart/modules/test_firmware.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio import logging -from contextlib import nullcontext +from contextlib import AbstractContextManager, nullcontext from datetime import date from typing import TypedDict @@ -31,9 +31,14 @@ firmware = parametrize( ], ) async def test_firmware_features( - dev: SmartDevice, feature, prop_name, type, required_version, mocker: MockerFixture -): - """Test light effect.""" + dev: SmartDevice, + feature: str, + prop_name: str, + type: type, + required_version: int, + mocker: MockerFixture, +) -> None: + """Test firmware features.""" fw = dev.modules.get(Module.Firmware) assert fw assert fw.firmware_update_info is None @@ -54,7 +59,7 @@ async def test_firmware_features( @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.""" fw = dev.modules.get(Module.Firmware) assert fw @@ -68,7 +73,7 @@ async def test_firmware_update_info(dev: SmartDevice): @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.""" fw = dev.modules.get(Module.Firmware) assert fw @@ -95,9 +100,9 @@ async def test_firmware_update( dev: SmartDevice, mocker: MockerFixture, caplog: pytest.LogCaptureFixture, - update_available, - expected_result, -): + update_available: bool, + expected_result: AbstractContextManager, +) -> None: """Test updating firmware.""" caplog.set_level(logging.INFO) diff --git a/tests/smart/modules/test_homekit.py b/tests/smart/modules/test_homekit.py index 81992398..745053ca 100644 --- a/tests/smart/modules/test_homekit.py +++ b/tests/smart/modules/test_homekit.py @@ -9,7 +9,7 @@ homekit = parametrize( @homekit -async def test_info(dev: SmartDevice): +async def test_info(dev: SmartDevice) -> None: """Test homekit info.""" homekit = dev.modules.get(Module.HomeKit) assert homekit diff --git a/tests/smart/modules/test_humidity.py b/tests/smart/modules/test_humidity.py index 5e14a05b..f5df226b 100644 --- a/tests/smart/modules/test_humidity.py +++ b/tests/smart/modules/test_humidity.py @@ -1,6 +1,7 @@ import pytest -from kasa.smart.modules import HumiditySensor +from kasa import Module +from kasa.smart import SmartDevice from ...device_fixtures import parametrize @@ -17,9 +18,9 @@ humidity = parametrize( ("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.""" - humidity: HumiditySensor = dev.modules["HumiditySensor"] + humidity = dev.modules[Module.HumiditySensor] prop = getattr(humidity, feature) assert isinstance(prop, type) diff --git a/tests/smart/modules/test_light_effect.py b/tests/smart/modules/test_light_effect.py index e4475652..0e522503 100644 --- a/tests/smart/modules/test_light_effect.py +++ b/tests/smart/modules/test_light_effect.py @@ -16,7 +16,7 @@ light_effect = parametrize( @light_effect -async def test_light_effect(dev: Device, mocker: MockerFixture): +async def test_light_effect(dev: Device, mocker: MockerFixture) -> None: """Test light effect.""" light_effect = dev.modules.get(Module.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]) async def test_light_effect_brightness( dev: Device, effect_active: bool, mocker: MockerFixture -): +) -> None: """Test that light module uses light_effect for brightness when active.""" light_module = dev.modules[Module.Light] diff --git a/tests/smart/modules/test_light_strip_effect.py b/tests/smart/modules/test_light_strip_effect.py index 81bc35c8..7cd5481e 100644 --- a/tests/smart/modules/test_light_strip_effect.py +++ b/tests/smart/modules/test_light_strip_effect.py @@ -18,7 +18,7 @@ light_strip_effect = parametrize( @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.""" 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]) async def test_light_effect_brightness( dev: Device, effect_active: bool, mocker: MockerFixture -): +) -> None: """Test that light module uses light_effect for brightness when active.""" light_module = dev.modules[Module.Light] diff --git a/tests/smart/modules/test_lighttransition.py b/tests/smart/modules/test_lighttransition.py index c1b805e4..2ef5a9e7 100644 --- a/tests/smart/modules/test_lighttransition.py +++ b/tests/smart/modules/test_lighttransition.py @@ -23,7 +23,7 @@ light_transition_gt_v1 = parametrize( @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.""" assert isinstance(dev, SmartDevice) 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 -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.""" assert isinstance(dev, SmartDevice) light_transition = next(get_parent_and_child_modules(dev, Module.LightTransition)) diff --git a/tests/smart/modules/test_matter.py b/tests/smart/modules/test_matter.py index d3ff8073..43f872c0 100644 --- a/tests/smart/modules/test_matter.py +++ b/tests/smart/modules/test_matter.py @@ -9,7 +9,7 @@ matter = parametrize( @matter -async def test_info(dev: SmartDevice): +async def test_info(dev: SmartDevice) -> None: """Test matter info.""" matter = dev.modules.get(Module.Matter) assert matter diff --git a/tests/smart/modules/test_mop.py b/tests/smart/modules/test_mop.py index b6492aa3..566a62f8 100644 --- a/tests/smart/modules/test_mop.py +++ b/tests/smart/modules/test_mop.py @@ -20,7 +20,9 @@ mop = parametrize("has mop", component_filter="mop", protocol_filter={"SMART"}) ("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.""" mod = next(get_parent_and_child_modules(dev, Module.Mop)) assert mod is not None @@ -34,7 +36,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty @mop -async def test_mop_waterlevel(dev: SmartDevice, mocker: MockerFixture): +async def test_mop_waterlevel(dev: SmartDevice, mocker: MockerFixture) -> None: """Test dust mode.""" mop_module = next(get_parent_and_child_modules(dev, Module.Mop)) call = mocker.spy(mop_module, "call") diff --git a/tests/smart/modules/test_motionsensor.py b/tests/smart/modules/test_motionsensor.py index 418ad51a..80f76586 100644 --- a/tests/smart/modules/test_motionsensor.py +++ b/tests/smart/modules/test_motionsensor.py @@ -16,7 +16,7 @@ motion = parametrize( ("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.""" motion = dev.modules.get(Module.MotionSensor) assert motion is not None diff --git a/tests/smart/modules/test_powerprotection.py b/tests/smart/modules/test_powerprotection.py index 215df2be..db0a5d93 100644 --- a/tests/smart/modules/test_powerprotection.py +++ b/tests/smart/modules/test_powerprotection.py @@ -2,6 +2,7 @@ import pytest from pytest_mock import MockerFixture from kasa import Device, Module +from kasa.smart import SmartDevice from ...device_fixtures import get_parent_and_child_modules, parametrize @@ -20,7 +21,9 @@ powerprotection = parametrize( ("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.""" powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection)) assert powerprot @@ -35,7 +38,7 @@ async def test_features(dev, feature, prop_name, type): @powerprotection -async def test_set_enable(dev: Device, mocker: MockerFixture): +async def test_set_enable(dev: Device, mocker: MockerFixture) -> None: """Test enable.""" powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection)) assert powerprot @@ -88,7 +91,7 @@ async def test_set_enable(dev: Device, mocker: MockerFixture): @powerprotection -async def test_set_threshold(dev: Device, mocker: MockerFixture): +async def test_set_threshold(dev: Device, mocker: MockerFixture) -> None: """Test enable.""" powerprot = next(get_parent_and_child_modules(dev, Module.PowerProtection)) assert powerprot diff --git a/tests/smart/modules/test_speaker.py b/tests/smart/modules/test_speaker.py index e11741da..b0aef265 100644 --- a/tests/smart/modules/test_speaker.py +++ b/tests/smart/modules/test_speaker.py @@ -20,7 +20,9 @@ speaker = parametrize( ("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.""" speaker = next(get_parent_and_child_modules(dev, Module.Speaker)) assert speaker is not None @@ -34,7 +36,7 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty @speaker -async def test_set_volume(dev: SmartDevice, mocker: MockerFixture): +async def test_set_volume(dev: SmartDevice, mocker: MockerFixture) -> None: """Test speaker settings.""" speaker = next(get_parent_and_child_modules(dev, Module.Speaker)) assert speaker is not None @@ -61,7 +63,7 @@ async def test_set_volume(dev: SmartDevice, mocker: MockerFixture): @speaker -async def test_locate(dev: SmartDevice, mocker: MockerFixture): +async def test_locate(dev: SmartDevice, mocker: MockerFixture) -> None: """Test the locate method.""" speaker = next(get_parent_and_child_modules(dev, Module.Speaker)) call = mocker.spy(speaker, "call") diff --git a/tests/smart/modules/test_temperature.py b/tests/smart/modules/test_temperature.py index c2f91ae1..814b07c4 100644 --- a/tests/smart/modules/test_temperature.py +++ b/tests/smart/modules/test_temperature.py @@ -1,6 +1,7 @@ import pytest -from kasa.smart.modules import TemperatureSensor +from kasa import Module +from kasa.smart import SmartDevice from ...device_fixtures import parametrize @@ -23,9 +24,9 @@ temperature_warning = parametrize( ("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.""" - temp_module: TemperatureSensor = dev.modules["TemperatureSensor"] + temp_module = dev.modules[Module.TemperatureSensor] prop = getattr(temp_module, feature) assert isinstance(prop, type) @@ -36,9 +37,9 @@ async def test_temperature_features(dev, feature, type): @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.""" - temp_module: TemperatureSensor = dev.modules["TemperatureSensor"] + temp_module = dev.modules[Module.TemperatureSensor] assert hasattr(temp_module, "temperature_warning") assert isinstance(temp_module.temperature_warning, bool) diff --git a/tests/smart/modules/test_temperaturecontrol.py b/tests/smart/modules/test_temperaturecontrol.py index 4bcf218f..49118df0 100644 --- a/tests/smart/modules/test_temperaturecontrol.py +++ b/tests/smart/modules/test_temperaturecontrol.py @@ -3,7 +3,8 @@ import re 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 ...device_fixtures import parametrize, thermostats_smart @@ -23,9 +24,11 @@ temperature = parametrize( ("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.""" - temp_module: TemperatureControl = dev.modules["TemperatureControl"] + temp_module = dev.modules[Module.TemperatureControl] prop = getattr(temp_module, feature) assert isinstance(prop, type) @@ -40,9 +43,9 @@ async def test_temperature_control_features(dev, feature, type): @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.""" - temp_module: TemperatureControl = dev.modules["TemperatureControl"] + temp_module = dev.modules[Module.TemperatureControl] await temp_module.set_state(False) await dev.update() @@ -57,9 +60,9 @@ async def test_set_temperature_turns_heating_on(dev): @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.""" - temp_module: TemperatureControl = dev.modules["TemperatureControl"] + temp_module = dev.modules[Module.TemperatureControl] with pytest.raises( ValueError, match="Invalid target temperature -1, must be in range" @@ -73,9 +76,9 @@ async def test_set_temperature_invalid_values(dev): @thermostats_smart -async def test_temperature_offset(dev): +async def test_temperature_offset(dev: SmartDevice) -> None: """Test the temperature offset API.""" - temp_module: TemperatureControl = dev.modules["TemperatureControl"] + temp_module = dev.modules[Module.TemperatureControl] with pytest.raises( 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"), ], ) -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.""" - temp_module: TemperatureControl = dev.modules["TemperatureControl"] + temp_module = dev.modules[Module.TemperatureControl] temp_module.data["frost_protection_on"] = frost_protection 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") -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.""" - temp_module: TemperatureControl = dev.modules["TemperatureControl"] + temp_module = dev.modules[Module.TemperatureControl] caplog.set_level(logging.WARNING) temp_module.data["trv_states"] = states @@ -149,17 +160,19 @@ async def test_thermostat_mode_warnings(dev, mode, states, msg, caplog): @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.""" - temp_module: TemperatureControl = dev.modules["TemperatureControl"] + temp_module = dev.modules[Module.TemperatureControl] temp_module.data["trv_states"] = ["low_battery", "heating"] assert temp_module.mode is ThermostatState.Heating @thermostats_smart -async def test_thermostat_idle_with_low_battery(dev, caplog): +async def test_thermostat_idle_with_low_battery( + dev: SmartDevice, caplog: pytest.LogCaptureFixture +) -> None: """Test that mode is reported correctly with extra states.""" - temp_module: TemperatureControl = dev.modules["TemperatureControl"] + temp_module = dev.modules[Module.TemperatureControl] temp_module.data["trv_states"] = ["low_battery"] with caplog.at_level(logging.WARNING): assert temp_module.mode is ThermostatState.Idle diff --git a/tests/smart/modules/test_triggerlogs.py b/tests/smart/modules/test_triggerlogs.py index c1d95721..f6aebcc0 100644 --- a/tests/smart/modules/test_triggerlogs.py +++ b/tests/smart/modules/test_triggerlogs.py @@ -10,7 +10,7 @@ triggerlogs = parametrize( @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.""" triggerlogs = dev.modules.get(Module.TriggerLogs) assert triggerlogs is not None diff --git a/tests/smart/modules/test_waterleak.py b/tests/smart/modules/test_waterleak.py index afae7dda..f7b94a8d 100644 --- a/tests/smart/modules/test_waterleak.py +++ b/tests/smart/modules/test_waterleak.py @@ -3,7 +3,8 @@ from enum import Enum 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 ...device_fixtures import parametrize @@ -28,10 +29,12 @@ async def parent(request): ("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.""" dev._parent = parent - waterleak: WaterleakSensor = dev.modules["WaterleakSensor"] + waterleak = dev.modules[Module.WaterleakSensor] prop = getattr(waterleak, prop_name) assert isinstance(prop, type) @@ -42,10 +45,10 @@ async def test_waterleak_properties(dev, parent, feature, prop_name, type): @waterleak -async def test_waterleak_features(dev, parent): +async def test_waterleak_features(dev: SmartDevice, parent: SmartDevice) -> None: """Test waterleak features.""" dev._parent = parent - waterleak: WaterleakSensor = dev.modules["WaterleakSensor"] + waterleak = dev.modules[Module.WaterleakSensor] assert "water_leak" in dev.features assert dev.features["water_leak"].value == waterleak.status diff --git a/tests/smart/test_smartdevice.py b/tests/smart/test_smartdevice.py index 155c2bdf..aea73381 100644 --- a/tests/smart/test_smartdevice.py +++ b/tests/smart/test_smartdevice.py @@ -44,7 +44,9 @@ hub_all = parametrize_combine([hubs_smart, hub_smartcam]) @device_smart @pytest.mark.requires_dummy @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 = { "get_device_info": SmartErrorCode.PARAMS_ERROR, } @@ -56,7 +58,7 @@ async def test_try_get_response(dev: SmartDevice, caplog): @device_smart @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 = { "get_device_usage": {}, "get_device_time": {}, @@ -68,7 +70,9 @@ async def test_update_no_device_info(dev: SmartDevice, mocker: MockerFixture): @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.""" dev = SmartDevice(DISCOVERY_MOCK_IP) 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 -async def test_initial_update(dev: SmartDevice, mocker: MockerFixture): +async def test_initial_update(dev: SmartDevice, mocker: MockerFixture) -> None: """Test the initial update cycle.""" # As the fixture data is already initialized, we reset the state for testing dev._components_raw = None @@ -124,7 +128,7 @@ async def test_initial_update(dev: SmartDevice, mocker: MockerFixture): @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.""" # As the fixture data is already initialized, we reset the state for testing dev._components_raw = None @@ -158,7 +162,7 @@ async def test_negotiate(dev: SmartDevice, mocker: MockerFixture): @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.""" # We need to have some modules initialized by now assert dev._modules @@ -195,7 +199,7 @@ async def test_update_module_update_delays( mocker: MockerFixture, caplog: pytest.LogCaptureFixture, freezer: FrozenDateTimeFactory, -): +) -> None: """Test that modules with minimum delays delay.""" # We need to have some modules initialized by now assert dev._modules @@ -253,7 +257,7 @@ async def test_hub_children_update_delays( mocker: MockerFixture, caplog: pytest.LogCaptureFixture, freezer: FrozenDateTimeFactory, -): +) -> None: """Test that hub children use the correct delay.""" if not dev.children: pytest.skip(f"Device {dev.model} does not have children.") @@ -416,14 +420,15 @@ async def test_update_module_query_errors( mocker: MockerFixture, caplog: pytest.LogCaptureFixture, freezer: FrozenDateTimeFactory, - first_update, - error_type, - recover, -): + first_update: bool, + error_type: SmartErrorCode | TimeoutError, + recover: bool, +) -> None: """Test that modules that disabled / removed on query failures. 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 assert dev._modules @@ -480,8 +485,6 @@ async def test_update_module_query_errors( raise TimeoutError() if len(child_requests) > 1: raise TimeoutError() - - if raise_error: resp = { "method": child_requests[0]["method"], "error_code": error_type.value, @@ -594,7 +597,7 @@ async def test_update_module_query_errors( 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.""" dummy_device = await get_device_for_fixture_protocol( "KS240(US)_1.0_1.0.5.json", "SMART" @@ -629,7 +632,9 @@ async def test_get_modules(): @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.""" assert isinstance(dev, SmartDevice) assert "cloud_connect" in dev._components @@ -697,7 +702,7 @@ async def test_smartdevice_cloud_connection(dev: SmartDevice, mocker: MockerFixt @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) assert light color_temp_feat = light.get_feature("color_temp") @@ -708,7 +713,7 @@ async def test_smart_temp_range(dev: Device): @device_smart async def test_initialize_modules_sysinfo_lookup_keys( dev: SmartDevice, mocker: MockerFixture -): +) -> None: """Test that matching modules using SYSINFO_LOOKUP_KEYS are initialized correctly.""" class AvailableKey(SmartModule): @@ -737,7 +742,7 @@ async def test_initialize_modules_sysinfo_lookup_keys( @device_smart async def test_initialize_modules_required_component( dev: SmartDevice, mocker: MockerFixture -): +) -> None: """Test that matching modules using REQUIRED_COMPONENT are initialized correctly.""" class AvailableComponent(SmartModule): @@ -763,7 +768,7 @@ async def test_initialize_modules_required_component( 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.""" class DummyModule(SmartModule): @@ -779,7 +784,7 @@ async def test_smartmodule_query(): @hub_all @pytest.mark.xdist_group(name="caplog") @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.""" if not dev.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 -async def test_unpair(dev: SmartDevice, mocker: MockerFixture): +async def test_unpair(dev: SmartDevice, mocker: MockerFixture) -> None: """Verify that unpair calls childsetup module.""" if not dev.children: pytest.skip("device has no children")