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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ from ...device_fixtures import device_iot
@device_iot @device_iot
@pytest.mark.xdist_group(name="caplog") @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) schedule = dev.modules.get(Module.IotSchedule)
assert schedule assert schedule
if rules := schedule.rules: if rules := schedule.rules:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import pytest
from pytest_mock import MockerFixture 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.""" """Exercise _expected_dst_behavior_for_index for several representative indices."""
from kasa.iot.iottimezone import _expected_dst_behavior_for_index 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) _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.""" """When no ZoneInfo matches, return a fixed-offset tzinfo."""
import kasa.iot.iottimezone as tzmod 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 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).""" """Cover naive when_utc branch and candidate selection path (non-empty candidates)."""
import kasa.iot.iottimezone as tzmod 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( async def test_guess_timezone_by_offset_dst_expected_true_filters(
mocker: MockerFixture, mocker: MockerFixture,
): ) -> None:
"""dst_expected=True should prefer a DST-observing zone when possible.""" """dst_expected=True should prefer a DST-observing zone when possible."""
import kasa.iot.iottimezone as tzmod 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 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).""" """dst_expected=False should prefer a non-DST zone and skip DST candidates (covers False branch)."""
import kasa.iot.iottimezone as tzmod 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( async def test_guess_timezone_by_offset_handles_missing_zoneinfo_unit(
mocker: MockerFixture, mocker: MockerFixture,
): ) -> None:
"""Cover the ZoneInfoNotFoundError continue path within guess_timezone_by_offset.""" """Cover the ZoneInfoNotFoundError continue path within guess_timezone_by_offset."""
from zoneinfo import ZoneInfoNotFoundError as ZNF 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) 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.""" """If ZoneInfo key is in TIMEZONE_INDEX, return index directly."""
import kasa.iot.iottimezone as tzmod 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 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.""" """Exercise get_timezone_index path when input tzinfo is not a ZoneInfo instance."""
import kasa.iot.iottimezone as tzmod import kasa.iot.iottimezone as tzmod
@@ -119,7 +119,7 @@ async def test_get_timezone_index_non_zoneinfo_unit():
assert 0 <= idx <= 109 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.""" """Cover ZoneInfoNotFoundError path in get_timezone_index loop and successful match."""
from zoneinfo import ZoneInfoNotFoundError as ZNF from zoneinfo import ZoneInfoNotFoundError as ZNF
@@ -141,7 +141,7 @@ async def test_get_timezone_index_skips_missing_unit(mocker: MockerFixture):
assert idx >= 5 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).""" """Ensure get_timezone_index completes loop and raises when no match exists (covers raise branch)."""
import kasa.iot.iottimezone as tzmod 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))) 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.""" """Cover initial append, except path, and duplicate suppression in get_matching_timezones."""
from zoneinfo import ZoneInfoNotFoundError as ZNF 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 # 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).""" """Exercise get_matching_timezones when input tzinfo is not a ZoneInfo (skips initial append)."""
import kasa.iot.iottimezone as tzmod import kasa.iot.iottimezone as tzmod
@@ -181,7 +181,7 @@ async def test_get_matching_timezones_non_zoneinfo_unit():
assert len(matches) > 0 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.""" """Out-of-range index should log and default to UTC."""
import kasa.iot.iottimezone as tzmod import kasa.iot.iottimezone as tzmod

View File

@@ -1,8 +1,10 @@
from kasa.iot import IotWallSwitch
from ..device_fixtures import wallswitch_iot from ..device_fixtures import wallswitch_iot
@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.""" """Check that wallswitches with motion sensor get modules enabled."""
has_motion = "PIR" in dev.sys_info["dev_name"] has_motion = "PIR" in dev.sys_info["dev_name"]
assert "motion" in dev.modules if has_motion else True assert "motion" in dev.modules if has_motion else True