tests: add type annotations to IoT tests (#1685)

Add type annotations to all test functions across 8 files in `tests/iot/`, improving type safety and enabling mypy to check function bodies.
This commit is contained in:
ZeliardM
2026-07-05 12:31:48 -04:00
committed by GitHub
parent bb27a43027
commit da2588291d
15 changed files with 151 additions and 126 deletions

View File

@@ -8,7 +8,7 @@ from ...device_fixtures import dimmer_iot
@dimmer_iot
def test_ambientlight_getters(dev: IotDimmer):
def test_ambientlight_getters(dev: IotDimmer) -> None:
assert Module.IotAmbientLight in dev.modules
ambientlight: AmbientLight = dev.modules[Module.IotAmbientLight]
@@ -22,7 +22,7 @@ def test_ambientlight_getters(dev: IotDimmer):
@dimmer_iot
async def test_ambientlight_setters(dev: IotDimmer, mocker: MockerFixture):
async def test_ambientlight_setters(dev: IotDimmer, mocker: MockerFixture) -> None:
ambientlight: AmbientLight = dev.modules[Module.IotAmbientLight]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -36,7 +36,7 @@ async def test_ambientlight_setters(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
def test_ambientlight_feature(dev: IotDimmer):
def test_ambientlight_feature(dev: IotDimmer) -> None:
assert Module.IotAmbientLight in dev.modules
ambientlight: AmbientLight = dev.modules[Module.IotAmbientLight]

View File

@@ -4,7 +4,7 @@ from ...device_fixtures import device_iot
@device_iot
def test_cloud(dev: Device):
def test_cloud(dev: Device) -> None:
cloud = dev.modules.get(Module.IotCloud)
assert cloud
info = cloud.info

View File

@@ -14,7 +14,7 @@ _TD_ONE_MS: Final[timedelta] = timedelta(milliseconds=1)
@dimmer_iot
def test_dimmer_getters(dev: IotDimmer):
def test_dimmer_getters(dev: IotDimmer) -> None:
assert Module.IotDimmer in dev.modules
dimmer: Dimmer = dev.modules[Module.IotDimmer]
@@ -27,7 +27,7 @@ def test_dimmer_getters(dev: IotDimmer):
@dimmer_iot
async def test_dimmer_setters(dev: IotDimmer, mocker: MockerFixture):
async def test_dimmer_setters(dev: IotDimmer, mocker: MockerFixture) -> None:
dimmer: Dimmer = dev.modules[Module.IotDimmer]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -65,7 +65,7 @@ async def test_dimmer_setters(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
async def test_dimmer_setter_min(dev: IotDimmer, mocker: MockerFixture):
async def test_dimmer_setter_min(dev: IotDimmer, mocker: MockerFixture) -> None:
dimmer: Dimmer = dev.modules[Module.IotDimmer]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -103,7 +103,7 @@ async def test_dimmer_setter_min(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
async def test_dimmer_setter_max(dev: IotDimmer, mocker: MockerFixture):
async def test_dimmer_setter_max(dev: IotDimmer, mocker: MockerFixture) -> None:
dimmer: Dimmer = dev.modules[Module.IotDimmer]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -141,7 +141,7 @@ async def test_dimmer_setter_max(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
async def test_dimmer_setters_min_oob(dev: IotDimmer, mocker: MockerFixture):
async def test_dimmer_setters_min_oob(dev: IotDimmer, mocker: MockerFixture) -> None:
dimmer: Dimmer = dev.modules[Module.IotDimmer]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -173,7 +173,7 @@ async def test_dimmer_setters_min_oob(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
async def test_dimmer_setters_max_oob(dev: IotDimmer, mocker: MockerFixture):
async def test_dimmer_setters_max_oob(dev: IotDimmer, mocker: MockerFixture) -> None:
dimmer: Dimmer = dev.modules[Module.IotDimmer]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")

View File

@@ -12,7 +12,7 @@ from voluptuous import (
from kasa import Device, DeviceType, EmeterStatus, Module
from kasa.interfaces.energy import Energy
from kasa.iot import IotStrip
from kasa.iot import IotDevice, IotStrip
from kasa.iot.modules.emeter import Emeter
from tests.conftest import has_emeter_iot, no_emeter_iot
@@ -37,22 +37,22 @@ CURRENT_CONSUMPTION_SCHEMA = Schema(
@no_emeter_iot
async def test_no_emeter(dev):
async def test_no_emeter(dev: IotDevice) -> None:
assert not dev.has_emeter
with pytest.raises(AttributeError):
await dev.get_emeter_realtime()
await dev.get_emeter_realtime() # type: ignore[attr-defined]
with pytest.raises(AttributeError):
await dev.get_emeter_daily()
await dev.get_emeter_daily() # type: ignore[attr-defined]
with pytest.raises(AttributeError):
await dev.get_emeter_monthly()
await dev.get_emeter_monthly() # type: ignore[attr-defined]
with pytest.raises(AttributeError):
await dev.erase_emeter_stats()
await dev.erase_emeter_stats() # type: ignore[attr-defined]
@has_emeter_iot
async def test_get_emeter_realtime(dev):
async def test_get_emeter_realtime(dev: IotDevice) -> None:
emeter = dev.modules[Module.Energy]
current_emeter = await emeter.get_status()
@@ -65,7 +65,7 @@ async def test_get_emeter_realtime(dev):
@has_emeter_iot
@pytest.mark.requires_dummy
async def test_get_emeter_daily(dev):
async def test_get_emeter_daily(dev: IotDevice) -> None:
emeter = dev.modules[Module.Energy]
assert await emeter.get_daily_stats(year=1900, month=1) == {}
@@ -85,7 +85,7 @@ async def test_get_emeter_daily(dev):
@has_emeter_iot
@pytest.mark.requires_dummy
async def test_get_emeter_monthly(dev):
async def test_get_emeter_monthly(dev: IotDevice) -> None:
emeter = dev.modules[Module.Energy]
assert await emeter.get_monthly_stats(year=1900) == {}
@@ -104,7 +104,7 @@ async def test_get_emeter_monthly(dev):
@has_emeter_iot
async def test_emeter_status(dev):
async def test_emeter_status(dev: IotDevice) -> None:
emeter = dev.modules[Module.Energy]
d = await emeter.get_status()
@@ -126,21 +126,21 @@ async def test_emeter_status(dev):
@pytest.mark.skip("not clearing your stats..")
@has_emeter_iot
async def test_erase_emeter_stats(dev):
async def test_erase_emeter_stats(dev: IotDevice) -> None:
emeter = dev.modules[Module.Energy]
await emeter.erase_emeter()
@has_emeter_iot
async def test_current_consumption(dev):
async def test_current_consumption(dev: IotDevice) -> None:
emeter = dev.modules[Module.Energy]
x = emeter.current_consumption
assert isinstance(x, float)
assert x >= 0.0
async def test_emeterstatus_missing_current():
async def test_emeterstatus_missing_current() -> None:
"""KL125 does not report 'current' for emeter."""
regular = EmeterStatus(
{"err_code": 0, "power_mw": 0, "total_wh": 13, "current_ma": 123}
@@ -154,13 +154,13 @@ async def test_emeterstatus_missing_current():
assert missing_current["current"] is None
async def test_emeter_daily():
async def test_emeter_daily() -> None:
"""Test fetching the emeter for today.
This test uses inline data since the fixtures
will not have data for the current day.
"""
emeter_data = {
emeter_data: dict = {
"get_daystat": {
"day_list": [{"day": 1, "energy_wh": 8, "month": 1, "year": 2023}],
"err_code": 0,
@@ -181,7 +181,7 @@ async def test_emeter_daily():
@has_emeter_iot
async def test_supported(dev: Device):
async def test_supported(dev: Device) -> None:
energy_module = dev.modules.get(Module.Energy)
assert energy_module

View File

@@ -10,7 +10,7 @@ from ...device_fixtures import device_iot
@device_iot
def test_homekit_getters(dev: IotDevice):
def test_homekit_getters(dev: IotDevice) -> None:
# HomeKit can be present on any IOT device
if Module.IotHomeKit not in dev.modules:
pytest.skip("HomeKit module not present on this device")
@@ -31,7 +31,7 @@ def test_homekit_getters(dev: IotDevice):
@device_iot
def test_homekit_feature(dev: IotDevice):
def test_homekit_feature(dev: IotDevice) -> None:
if Module.IotHomeKit not in dev.modules:
pytest.skip("HomeKit module not present on this device")
homekit: HomeKit = dev.modules[Module.IotHomeKit]
@@ -45,7 +45,7 @@ def test_homekit_feature(dev: IotDevice):
@device_iot
def test_initialize_features_skips_when_no_data(dev: IotDevice):
def test_initialize_features_skips_when_no_data(dev: IotDevice) -> None:
if Module.IotHomeKit not in dev.modules:
pytest.skip("HomeKit module not present on this device")
homekit: HomeKit = dev.modules[Module.IotHomeKit]

View File

@@ -9,7 +9,7 @@ from ...device_fixtures import dimmer_iot
@dimmer_iot
def test_motion_getters(dev: IotDimmer):
def test_motion_getters(dev: IotDimmer) -> None:
assert Module.IotMotion in dev.modules
motion: Motion = dev.modules[Module.IotMotion]
@@ -19,7 +19,7 @@ def test_motion_getters(dev: IotDimmer):
@dimmer_iot
async def test_motion_setters(dev: IotDimmer, mocker: MockerFixture):
async def test_motion_setters(dev: IotDimmer, mocker: MockerFixture) -> None:
motion: Motion = dev.modules[Module.IotMotion]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -33,7 +33,7 @@ async def test_motion_setters(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
async def test_motion_range(dev: IotDimmer, mocker: MockerFixture):
async def test_motion_range(dev: IotDimmer, mocker: MockerFixture) -> None:
motion: Motion = dev.modules[Module.IotMotion]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -47,7 +47,7 @@ async def test_motion_range(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
async def test_motion_range_from_string(dev: IotDimmer, mocker: MockerFixture):
async def test_motion_range_from_string(dev: IotDimmer, mocker: MockerFixture) -> None:
motion: Motion = dev.modules[Module.IotMotion]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -74,7 +74,7 @@ async def test_motion_range_from_string(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
async def test_motion_threshold(dev: IotDimmer, mocker: MockerFixture):
async def test_motion_threshold(dev: IotDimmer, mocker: MockerFixture) -> None:
motion: Motion = dev.modules[Module.IotMotion]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -97,7 +97,7 @@ async def test_motion_threshold(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
async def test_motion_realtime(dev: IotDimmer, mocker: MockerFixture):
async def test_motion_realtime(dev: IotDimmer, mocker: MockerFixture) -> None:
motion: Motion = dev.modules[Module.IotMotion]
query_helper = mocker.patch("kasa.iot.IotDimmer._query_helper")
@@ -106,7 +106,7 @@ async def test_motion_realtime(dev: IotDimmer, mocker: MockerFixture):
@dimmer_iot
def test_motion_feature(dev: IotDimmer):
def test_motion_feature(dev: IotDimmer) -> None:
assert Module.IotMotion in dev.modules
motion: Motion = dev.modules[Module.IotMotion]

View File

@@ -8,7 +8,7 @@ from ...device_fixtures import device_iot
@device_iot
@pytest.mark.xdist_group(name="caplog")
def test_schedule(dev: Device, caplog: pytest.LogCaptureFixture):
def test_schedule(dev: Device, caplog: pytest.LogCaptureFixture) -> None:
schedule = dev.modules.get(Module.IotSchedule)
assert schedule
if rules := schedule.rules:

View File

@@ -4,10 +4,10 @@ from unittest.mock import Mock
from kasa.iot.modules import Usage
def test_usage_convert_stat_data():
usage = Usage(None, module="usage")
def test_usage_convert_stat_data() -> None:
usage = Usage(Mock(), module="usage")
test_data = []
test_data: list[dict[str, int]] = []
assert usage._convert_stat_data(test_data, "day") == {}
test_data = [
@@ -24,13 +24,13 @@ def test_usage_convert_stat_data():
assert v == 30
def test_usage_today():
def test_usage_today() -> None:
"""Test fetching the usage for today.
This test uses inline data since the fixtures
will not have data for the current day.
"""
emeter_data = {
emeter_data: dict = {
"get_daystat": {
"day_list": [],
"err_code": 0,
@@ -55,13 +55,13 @@ def test_usage_today():
assert usage.usage_today == 500
def test_usage_this_month():
def test_usage_this_month() -> None:
"""Test fetching the usage for this month.
This test uses inline data since the fixtures
will not have data for the current month.
"""
emeter_data = {
emeter_data: dict = {
"get_monthstat": {
"month_list": [],
"err_code": 0,

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import re
import pytest
from pytest_mock import MockerFixture
from voluptuous import (
All,
Boolean,
@@ -27,7 +28,7 @@ from tests.iot.test_iotdevice import SYSINFO_SCHEMA
@bulb_iot
async def test_bulb_sysinfo(dev: Device):
async def test_bulb_sysinfo(dev: Device) -> None:
assert dev.sys_info is not None
SYSINFO_SCHEMA_BULB(dev.sys_info)
@@ -35,19 +36,21 @@ async def test_bulb_sysinfo(dev: Device):
@bulb_iot
async def test_light_state_without_update(dev: IotBulb, monkeypatch):
async def test_light_state_without_update(
dev: IotBulb, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setitem(dev._last_update["system"]["get_sysinfo"], "light_state", None)
with pytest.raises(KasaException):
print(dev.light_state)
@bulb_iot
async def test_get_light_state(dev: IotBulb):
async def test_get_light_state(dev: IotBulb) -> None:
LIGHT_STATE_SCHEMA(await dev.get_light_state())
@color_bulb_iot
async def test_set_hsv_transition(dev: IotBulb, mocker):
async def test_set_hsv_transition(dev: IotBulb, mocker: MockerFixture) -> None:
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
light = dev.modules.get(Module.Light)
assert light
@@ -60,7 +63,7 @@ async def test_set_hsv_transition(dev: IotBulb, mocker):
@bulb_iot
async def test_light_set_state(dev: IotBulb, mocker):
async def test_light_set_state(dev: IotBulb, mocker: MockerFixture) -> None:
"""Testing setting LightState on the light module."""
light = dev.modules.get(Module.Light)
assert light
@@ -76,7 +79,7 @@ async def test_light_set_state(dev: IotBulb, mocker):
@variable_temp_iot
async def test_set_color_temp_transition(dev: IotBulb, mocker):
async def test_set_color_temp_transition(dev: IotBulb, mocker: MockerFixture) -> None:
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
light = dev.modules.get(Module.Light)
assert light
@@ -87,7 +90,9 @@ async def test_set_color_temp_transition(dev: IotBulb, mocker):
@variable_temp_iot
@pytest.mark.xdist_group(name="caplog")
async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
async def test_unknown_temp_range(
dev: IotBulb, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
light = dev.modules.get(Module.Light)
assert light
@@ -99,7 +104,7 @@ async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
@dimmable_iot
@turn_on
async def test_dimmable_brightness(dev: IotBulb, turn_on):
async def test_dimmable_brightness(dev: IotBulb, turn_on: bool) -> None:
assert isinstance(dev, IotBulb | IotDimmer)
light = dev.modules.get(Module.Light)
assert light
@@ -119,7 +124,7 @@ async def test_dimmable_brightness(dev: IotBulb, turn_on):
@bulb_iot
async def test_turn_on_transition(dev: IotBulb, mocker):
async def test_turn_on_transition(dev: IotBulb, mocker: MockerFixture) -> None:
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
await dev.turn_on(transition=1000)
@@ -131,7 +136,9 @@ async def test_turn_on_transition(dev: IotBulb, mocker):
@bulb_iot
async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
async def test_dimmable_brightness_transition(
dev: IotBulb, mocker: MockerFixture
) -> None:
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
light = dev.modules.get(Module.Light)
assert light
@@ -141,7 +148,7 @@ async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
@dimmable_iot
async def test_invalid_brightness(dev: IotBulb):
async def test_invalid_brightness(dev: IotBulb) -> None:
assert dev._is_dimmable
light = dev.modules.get(Module.Light)
assert light
@@ -159,7 +166,7 @@ async def test_invalid_brightness(dev: IotBulb):
@non_dimmable_iot
async def test_non_dimmable(dev: IotBulb):
async def test_non_dimmable(dev: IotBulb) -> None:
assert not dev._is_dimmable
light = dev.modules.get(Module.Light)
assert light
@@ -171,8 +178,8 @@ async def test_non_dimmable(dev: IotBulb):
@bulb_iot
async def test_ignore_default_not_set_without_color_mode_change_turn_on(
dev: IotBulb, mocker
):
dev: IotBulb, mocker: MockerFixture
) -> None:
query_helper = mocker.patch("kasa.iot.IotBulb._query_helper")
# When turning back without settings, ignore default to restore the state
await dev.turn_on()
@@ -185,7 +192,7 @@ async def test_ignore_default_not_set_without_color_mode_change_turn_on(
@bulb_iot
async def test_list_presets(dev: IotBulb):
async def test_list_presets(dev: IotBulb) -> None:
light_preset = dev.modules.get(Module.LightPreset)
assert light_preset
assert isinstance(light_preset, IotLightPresetModule)
@@ -206,7 +213,7 @@ async def test_list_presets(dev: IotBulb):
@bulb_iot
async def test_modify_preset(dev: IotBulb, mocker):
async def test_modify_preset(dev: IotBulb, mocker: MockerFixture) -> None:
"""Verify that modifying preset calls the and exceptions are raised properly."""
if (
not (light_preset := dev.modules.get(Module.LightPreset))
@@ -254,7 +261,9 @@ async def test_modify_preset(dev: IotBulb, mocker):
),
],
)
async def test_modify_preset_payloads(dev: IotBulb, preset, payload, mocker):
async def test_modify_preset_payloads(
dev: IotBulb, preset, payload, mocker: MockerFixture
) -> None:
"""Test that modify preset payloads ignore none values."""
if (
not (light_preset := dev.modules.get(Module.LightPreset))
@@ -318,6 +327,6 @@ SYSINFO_SCHEMA_BULB = SYSINFO_SCHEMA.extend(
@bulb_iot
async def test_turn_on_behaviours(dev: IotBulb):
async def test_turn_on_behaviours(dev: IotBulb) -> None:
behavior = await dev.get_turn_on_behavior()
assert behavior

View File

@@ -4,6 +4,7 @@ import re
from datetime import datetime
import pytest
from pytest_mock import MockerFixture
from voluptuous import (
REMOVE_EXTRA,
All,
@@ -84,20 +85,20 @@ SYSINFO_SCHEMA = Schema(
@device_iot
async def test_state_info(dev):
async def test_state_info(dev: IotDevice) -> None:
assert isinstance(dev.state_information, dict)
@pytest.mark.requires_dummy
@device_iot
async def test_invalid_connection(mocker, dev):
async def test_invalid_connection(mocker: MockerFixture, dev: IotDevice) -> None:
mocker.patch.object(FakeIotProtocol, "query", side_effect=KasaException)
with pytest.raises(KasaException):
await dev.update()
@has_emeter_iot
async def test_initial_update_emeter(dev, mocker):
async def test_initial_update_emeter(dev: IotDevice, mocker: MockerFixture) -> None:
"""Test that the initial update performs second query if emeter is available."""
dev._last_update = {}
dev._legacy_features = set()
@@ -109,7 +110,7 @@ async def test_initial_update_emeter(dev, mocker):
@no_emeter_iot
async def test_initial_update_no_emeter(dev, mocker):
async def test_initial_update_no_emeter(dev: IotDevice, mocker: MockerFixture) -> None:
"""Test that the initial update performs second query if emeter is available."""
dev._last_update = {}
dev._legacy_features = set()
@@ -128,7 +129,7 @@ async def test_initial_update_no_emeter(dev, mocker):
@device_iot
async def test_query_helper(dev):
async def test_query_helper(dev: IotDevice) -> None:
with pytest.raises(KasaException):
await dev._query_helper("test", "testcmd", {})
# TODO check for unwrapping?
@@ -136,7 +137,7 @@ async def test_query_helper(dev):
@device_iot
@turn_on
async def test_state(dev, turn_on):
async def test_state(dev: IotDevice, turn_on: bool) -> None:
orig_state = dev.is_on
await handle_turn_on(dev, turn_on)
await dev.update()
@@ -164,7 +165,7 @@ async def test_state(dev, turn_on):
@device_iot
@turn_on
async def test_on_since(dev, turn_on):
async def test_on_since(dev: IotDevice, turn_on: bool) -> None:
await handle_turn_on(dev, turn_on)
orig_state = dev.is_on
if "on_time" not in dev.sys_info and dev.device_type is not DeviceType.Strip:
@@ -176,43 +177,43 @@ async def test_on_since(dev, turn_on):
@device_iot
async def test_time(dev):
async def test_time(dev: IotDevice) -> None:
assert isinstance(dev.modules[Module.Time].time, datetime)
@device_iot
async def test_timezone(dev):
async def test_timezone(dev: IotDevice) -> None:
TZ_SCHEMA(await dev.modules[Module.Time].get_timezone())
@device_iot
async def test_hw_info(dev):
async def test_hw_info(dev: IotDevice) -> None:
SYSINFO_SCHEMA(dev.hw_info)
@device_iot
async def test_location(dev):
async def test_location(dev: IotDevice) -> None:
SYSINFO_SCHEMA(dev.location)
@device_iot
async def test_rssi(dev):
async def test_rssi(dev: IotDevice) -> None:
SYSINFO_SCHEMA({"rssi": dev.rssi}) # wrapping for vol
@device_iot
async def test_mac(dev):
async def test_mac(dev: IotDevice) -> None:
SYSINFO_SCHEMA({"mac": dev.mac}) # wrapping for val
@device_iot
async def test_representation(dev):
async def test_representation(dev: IotDevice) -> None:
pattern = re.compile(r"<DeviceType\..+ at .+? - .*? \(.+?\)>")
assert pattern.match(str(dev))
@device_iot
async def test_children(dev):
async def test_children(dev: IotDevice) -> None:
"""Make sure that children property is exposed by every device."""
if dev.device_type is DeviceType.Strip:
assert len(dev.children) > 0
@@ -221,7 +222,7 @@ async def test_children(dev):
@device_iot
async def test_modules_preserved(dev: IotDevice):
async def test_modules_preserved(dev: IotDevice) -> None:
"""Make modules that are not being updated are preserved between updates."""
dev._last_update["some_module_not_being_updated"] = "should_be_kept"
await dev.update()
@@ -229,13 +230,13 @@ async def test_modules_preserved(dev: IotDevice):
@device_iot
async def test_internal_state(dev):
async def test_internal_state(dev: IotDevice) -> None:
"""Make sure the internal state returns the last update results."""
assert dev.internal_state == dev._last_update
@device_iot
async def test_features(dev):
async def test_features(dev: IotDevice) -> None:
"""Make sure features is always accessible."""
sysinfo = dev._last_update["system"]["get_sysinfo"]
if "feature" in sysinfo:
@@ -245,20 +246,20 @@ async def test_features(dev):
@device_iot
async def test_max_device_response_size(dev):
async def test_max_device_response_size(dev: IotDevice) -> None:
"""Make sure every device return has a set max response size."""
assert dev.max_device_response_size > 0
@device_iot
async def test_estimated_response_sizes(dev):
async def test_estimated_response_sizes(dev: IotDevice) -> None:
"""Make sure every module has an estimated response size set."""
for mod in dev.modules.values():
assert mod.estimated_query_response_size > 0
@device_iot
async def test_modules_not_supported(dev: IotDevice):
async def test_modules_not_supported(dev: IotDevice) -> None:
"""Test that unsupported modules do not break the device."""
for module in dev.modules.values():
assert module.is_supported is not None
@@ -267,7 +268,7 @@ async def test_modules_not_supported(dev: IotDevice):
assert module.is_supported 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(
"HS100(US)_2.0_1.5.6.json", "IOT"
@@ -293,10 +294,10 @@ async def test_get_modules():
assert module is None
def test_merge_dict():
def test_merge_dict() -> None:
"""Test the recursive dict merge."""
dest = {"a": 1, "b": {"c": 2, "d": 3}}
source = {"b": {"c": 4, "e": 5}}
dest: dict = {"a": 1, "b": {"c": 2, "d": 3}}
source: dict = {"b": {"c": 4, "e": 5}}
assert _merge_dict(dest, source) == {"a": 1, "b": {"c": 4, "d": 3, "e": 5}}
dest = {"smartlife.iot.common.emeter": {"get_realtime": None}}

View File

@@ -1,4 +1,5 @@
import pytest
from pytest_mock import MockerFixture
from kasa import DeviceType, Module
from kasa.iot import IotDimmer
@@ -6,7 +7,7 @@ from tests.conftest import dimmer_iot, handle_turn_on, turn_on
@dimmer_iot
async def test_set_brightness(dev):
async def test_set_brightness(dev: IotDimmer) -> None:
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, False)
@@ -26,7 +27,9 @@ async def test_set_brightness(dev):
@dimmer_iot
@turn_on
async def test_set_brightness_transition(dev, turn_on, mocker):
async def test_set_brightness_transition(
dev: IotDimmer, turn_on: bool, mocker: MockerFixture
) -> None:
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, turn_on)
@@ -49,7 +52,7 @@ async def test_set_brightness_transition(dev, turn_on, mocker):
@dimmer_iot
async def test_set_brightness_invalid(dev):
async def test_set_brightness_invalid(dev: IotDimmer) -> None:
light = dev.modules.get(Module.Light)
assert light
for invalid_brightness in [-1, 101]:
@@ -62,7 +65,7 @@ async def test_set_brightness_invalid(dev):
@dimmer_iot
async def test_set_brightness_invalid_transition(dev):
async def test_set_brightness_invalid_transition(dev: IotDimmer) -> None:
light = dev.modules.get(Module.Light)
assert light
for invalid_transition in [-1]:
@@ -74,7 +77,7 @@ async def test_set_brightness_invalid_transition(dev):
@dimmer_iot
async def test_turn_on_transition(dev, mocker):
async def test_turn_on_transition(dev: IotDimmer, mocker: MockerFixture) -> None:
light = dev.modules.get(Module.Light)
assert light
query_helper = mocker.spy(IotDimmer, "_query_helper")
@@ -93,7 +96,7 @@ async def test_turn_on_transition(dev, mocker):
@dimmer_iot
async def test_turn_off_transition(dev, mocker):
async def test_turn_off_transition(dev: IotDimmer, mocker: MockerFixture) -> None:
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, True)
@@ -115,7 +118,9 @@ async def test_turn_off_transition(dev, mocker):
@dimmer_iot
@turn_on
async def test_set_dimmer_transition(dev, turn_on, mocker):
async def test_set_dimmer_transition(
dev: IotDimmer, turn_on: bool, mocker: MockerFixture
) -> None:
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, turn_on)
@@ -135,7 +140,9 @@ async def test_set_dimmer_transition(dev, turn_on, mocker):
@dimmer_iot
@turn_on
async def test_set_dimmer_transition_to_off(dev, turn_on, mocker):
async def test_set_dimmer_transition_to_off(
dev: IotDimmer, turn_on: bool, mocker: MockerFixture
) -> None:
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, turn_on)
@@ -156,26 +163,26 @@ async def test_set_dimmer_transition_to_off(dev, turn_on, mocker):
@dimmer_iot
async def test_set_dimmer_transition_invalid_brightness(dev):
async def test_set_dimmer_transition_invalid_brightness(dev: IotDimmer) -> None:
for invalid_brightness in [-1, 101]:
with pytest.raises(ValueError, match="Invalid brightness value: "):
await dev.set_dimmer_transition(invalid_brightness, 1000)
for invalid_type in [0.5, "foo"]:
with pytest.raises(TypeError, match="Transition must be integer"):
await dev.set_dimmer_transition(1, invalid_type)
await dev.set_dimmer_transition(1, invalid_type) # type: ignore[arg-type]
@dimmer_iot
async def test_set_dimmer_transition_invalid_transition(dev):
async def test_set_dimmer_transition_invalid_transition(dev: IotDimmer) -> None:
for invalid_transition in [-1]:
with pytest.raises(ValueError, match="Transition value .+? is not valid."):
await dev.set_dimmer_transition(1, transition=invalid_transition)
for invalid_type in [0.5, "foo"]:
with pytest.raises(TypeError, match="Transition must be integer"):
await dev.set_dimmer_transition(1, transition=invalid_type)
await dev.set_dimmer_transition(1, transition=invalid_type) # type: ignore[arg-type]
@dimmer_iot
def test_device_type_dimmer(dev):
def test_device_type_dimmer(dev: IotDimmer) -> None:
assert dev.device_type == DeviceType.Dimmer

View File

@@ -1,4 +1,5 @@
import pytest
from pytest_mock import MockerFixture
from kasa import DeviceType, Module
from kasa.iot import IotLightStrip
@@ -7,13 +8,13 @@ from tests.conftest import lightstrip_iot
@lightstrip_iot
async def test_lightstrip_length(dev: IotLightStrip):
async def test_lightstrip_length(dev: IotLightStrip) -> None:
assert dev.device_type == DeviceType.LightStrip
assert dev.length == dev.sys_info["length"]
@lightstrip_iot
async def test_lightstrip_effect(dev: IotLightStrip):
async def test_lightstrip_effect(dev: IotLightStrip) -> None:
le: LightEffect = dev.modules[Module.LightEffect]
assert isinstance(le._deprecated_effect, dict)
for k in ["brightness", "custom", "enable", "id", "name"]:
@@ -21,7 +22,7 @@ async def test_lightstrip_effect(dev: IotLightStrip):
@lightstrip_iot
async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
async def test_effects_lightstrip_set_effect(dev: IotLightStrip) -> None:
le: LightEffect = dev.modules[Module.LightEffect]
with pytest.raises(
ValueError, match="The effect Not real is not a built in effect"
@@ -36,8 +37,8 @@ async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
@lightstrip_iot
@pytest.mark.parametrize("brightness", [100, 50])
async def test_effects_lightstrip_set_effect_brightness(
dev: IotLightStrip, brightness, mocker
):
dev: IotLightStrip, brightness: int, mocker: MockerFixture
) -> None:
query_helper = mocker.patch("kasa.iot.IotLightStrip._query_helper")
le: LightEffect = dev.modules[Module.LightEffect]
@@ -55,8 +56,8 @@ async def test_effects_lightstrip_set_effect_brightness(
@lightstrip_iot
@pytest.mark.parametrize("transition", [500, 1000])
async def test_effects_lightstrip_set_effect_transition(
dev: IotLightStrip, transition, mocker
):
dev: IotLightStrip, transition: int, mocker: MockerFixture
) -> None:
query_helper = mocker.patch("kasa.iot.IotLightStrip._query_helper")
le: LightEffect = dev.modules[Module.LightEffect]
@@ -72,12 +73,12 @@ async def test_effects_lightstrip_set_effect_transition(
@lightstrip_iot
async def test_effects_lightstrip_has_effects(dev: IotLightStrip):
async def test_effects_lightstrip_has_effects(dev: IotLightStrip) -> None:
le: LightEffect = dev.modules[Module.LightEffect]
assert le is not None
assert le.effect_list
@lightstrip_iot
def test_device_type_lightstrip(dev):
def test_device_type_lightstrip(dev: IotLightStrip) -> None:
assert dev.device_type == DeviceType.LightStrip

View File

@@ -1,11 +1,15 @@
from unittest.mock import AsyncMock
from pytest_mock import MockerFixture
from kasa import Module
from kasa.iot import IotStrip
from kasa.iot.iotstrip import IotStripPlug
from tests.conftest import strip_emeter_iot, strip_iot
@strip_iot
async def test_strip_update_and_child_update_behaviors(dev):
async def test_strip_update_and_child_update_behaviors(dev: IotStrip) -> None:
await dev.update()
await dev.update(update_children=False)
@@ -18,9 +22,10 @@ async def test_strip_update_and_child_update_behaviors(dev):
@strip_iot
async def test_strip_child_delegated_properties(dev):
async def test_strip_child_delegated_properties(dev: IotStrip) -> None:
await dev.update()
child = dev.children[0]
assert isinstance(child, IotStripPlug)
assert child.led is False
assert child.time == dev.time
@@ -32,7 +37,7 @@ async def test_strip_child_delegated_properties(dev):
@strip_emeter_iot
async def test_strip_emeter_erase_stats(dev, mocker):
async def test_strip_emeter_erase_stats(dev: IotStrip, mocker: MockerFixture) -> None:
await dev.update()
for child in dev.children:

View File

@@ -5,7 +5,7 @@ import pytest
from pytest_mock import MockerFixture
def test_expected_dst_behavior_for_index_cases():
def test_expected_dst_behavior_for_index_cases() -> None:
"""Exercise _expected_dst_behavior_for_index for several representative indices."""
from kasa.iot.iottimezone import _expected_dst_behavior_for_index
@@ -20,7 +20,7 @@ def test_expected_dst_behavior_for_index_cases():
_expected_dst_behavior_for_index(999)
async def test_guess_timezone_by_offset_fixed_fallback_unit():
async def test_guess_timezone_by_offset_fixed_fallback_unit() -> None:
"""When no ZoneInfo matches, return a fixed-offset tzinfo."""
import kasa.iot.iottimezone as tzmod
@@ -31,7 +31,7 @@ async def test_guess_timezone_by_offset_fixed_fallback_unit():
assert tz.utcoffset(when) == offset
async def test_guess_timezone_by_offset_candidates_unit():
async def test_guess_timezone_by_offset_candidates_unit() -> None:
"""Cover naive when_utc branch and candidate selection path (non-empty candidates)."""
import kasa.iot.iottimezone as tzmod
@@ -47,7 +47,7 @@ async def test_guess_timezone_by_offset_candidates_unit():
async def test_guess_timezone_by_offset_dst_expected_true_filters(
mocker: MockerFixture,
):
) -> None:
"""dst_expected=True should prefer a DST-observing zone when possible."""
import kasa.iot.iottimezone as tzmod
@@ -62,7 +62,7 @@ async def test_guess_timezone_by_offset_dst_expected_true_filters(
assert jan != jul # observes DST
async def test_guess_timezone_by_offset_dst_expected_false_prefers_non_dst():
async def test_guess_timezone_by_offset_dst_expected_false_prefers_non_dst() -> None:
"""dst_expected=False should prefer a non-DST zone and skip DST candidates (covers False branch)."""
import kasa.iot.iottimezone as tzmod
@@ -79,7 +79,7 @@ async def test_guess_timezone_by_offset_dst_expected_false_prefers_non_dst():
async def test_guess_timezone_by_offset_handles_missing_zoneinfo_unit(
mocker: MockerFixture,
):
) -> None:
"""Cover the ZoneInfoNotFoundError continue path within guess_timezone_by_offset."""
from zoneinfo import ZoneInfoNotFoundError as ZNF
@@ -101,7 +101,7 @@ async def test_guess_timezone_by_offset_handles_missing_zoneinfo_unit(
assert tz.utcoffset(when) == timedelta(0)
async def test_get_timezone_index_direct_match():
async def test_get_timezone_index_direct_match() -> None:
"""If ZoneInfo key is in TIMEZONE_INDEX, return index directly."""
import kasa.iot.iottimezone as tzmod
@@ -109,7 +109,7 @@ async def test_get_timezone_index_direct_match():
assert idx == 39 # "GB" is mapped to index 39
async def test_get_timezone_index_non_zoneinfo_unit():
async def test_get_timezone_index_non_zoneinfo_unit() -> None:
"""Exercise get_timezone_index path when input tzinfo is not a ZoneInfo instance."""
import kasa.iot.iottimezone as tzmod
@@ -119,7 +119,7 @@ async def test_get_timezone_index_non_zoneinfo_unit():
assert 0 <= idx <= 109
async def test_get_timezone_index_skips_missing_unit(mocker: MockerFixture):
async def test_get_timezone_index_skips_missing_unit(mocker: MockerFixture) -> None:
"""Cover ZoneInfoNotFoundError path in get_timezone_index loop and successful match."""
from zoneinfo import ZoneInfoNotFoundError as ZNF
@@ -141,7 +141,7 @@ async def test_get_timezone_index_skips_missing_unit(mocker: MockerFixture):
assert idx >= 5
async def test_get_timezone_index_raises_for_unmatched_unit():
async def test_get_timezone_index_raises_for_unmatched_unit() -> None:
"""Ensure get_timezone_index completes loop and raises when no match exists (covers raise branch)."""
import kasa.iot.iottimezone as tzmod
@@ -150,7 +150,7 @@ async def test_get_timezone_index_raises_for_unmatched_unit():
await tzmod.get_timezone_index(timezone(timedelta(minutes=2)))
async def test_get_matching_timezones_branches_unit(mocker: MockerFixture):
async def test_get_matching_timezones_branches_unit(mocker: MockerFixture) -> None:
"""Cover initial append, except path, and duplicate suppression in get_matching_timezones."""
from zoneinfo import ZoneInfoNotFoundError as ZNF
@@ -172,7 +172,7 @@ async def test_get_matching_timezones_branches_unit(mocker: MockerFixture):
# Loop should find GB again but not duplicate it
async def test_get_matching_timezones_non_zoneinfo_unit():
async def test_get_matching_timezones_non_zoneinfo_unit() -> None:
"""Exercise get_matching_timezones when input tzinfo is not a ZoneInfo (skips initial append)."""
import kasa.iot.iottimezone as tzmod
@@ -181,7 +181,7 @@ async def test_get_matching_timezones_non_zoneinfo_unit():
assert len(matches) > 0
async def test_get_timezone_out_of_range_defaults_to_utc():
async def test_get_timezone_out_of_range_defaults_to_utc() -> None:
"""Out-of-range index should log and default to UTC."""
import kasa.iot.iottimezone as tzmod

View File

@@ -1,8 +1,10 @@
from kasa.iot import IotWallSwitch
from ..device_fixtures import wallswitch_iot
@wallswitch_iot
def test_wallswitch_motion(dev):
def test_wallswitch_motion(dev: IotWallSwitch) -> None:
"""Check that wallswitches with motion sensor get modules enabled."""
has_motion = "PIR" in dev.sys_info["dev_name"]
assert "motion" in dev.modules if has_motion else True