Files
python-kasa/tests/smart/modules/test_alarm.py
ZeliardM 88e1c27bd0
Some checks are pending
CI / Perform Lint Checks (3.14) (push) Waiting to run
CI / Python 3.11 on macos-latest (push) Blocked by required conditions
CI / Python 3.12 on macos-latest (push) Blocked by required conditions
CI / Python 3.13 on macos-latest (push) Blocked by required conditions
CI / Python 3.14 on macos-latest (push) Blocked by required conditions
CI / Python 3.11 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.12 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.13 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.14 on ubuntu-latest (push) Blocked by required conditions
CI / Python 3.11 on windows-latest (push) Blocked by required conditions
CI / Python 3.12 on windows-latest (push) Blocked by required conditions
CI / Python 3.13 on windows-latest (push) Blocked by required conditions
CI / Python 3.14 on windows-latest (push) Blocked by required conditions
CodeQL Checks / Analyze (python) (push) Waiting to run
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.
2026-07-08 00:11:59 +02:00

147 lines
4.4 KiB
Python

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"),
[
("alarm", "active", bool),
("alarm_source", "source", str | None),
("alarm_sound", "alarm_sound", str),
("alarm_volume", "_alarm_volume_str", str),
("alarm_volume_level", "alarm_volume", int),
],
)
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
prop = getattr(alarm, prop_name)
assert isinstance(prop, type)
feat = alarm._device.features[feature]
assert feat.value == prop
assert isinstance(feat.value, type)
@alarm
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
volume_str_feat = alarm.get_feature("_alarm_volume_str")
assert volume_str_feat
assert volume_str_feat.choices == ["mute", "low", "normal", "high"]
volume_int_feat = alarm.get_feature("alarm_volume")
assert volume_int_feat.minimum_value == 0
assert volume_int_feat.maximum_value == 3
@alarm
@pytest.mark.parametrize(
("kwargs", "request_params"),
[
pytest.param({"volume": "low"}, {"alarm_volume": "low"}, id="volume"),
pytest.param({"volume": 0}, {"alarm_volume": "mute"}, id="volume-integer"),
pytest.param({"duration": 1}, {"alarm_duration": 1}, id="duration"),
pytest.param(
{"sound": "Doorbell Ring 1"}, {"alarm_type": "Doorbell Ring 1"}, id="sound"
),
],
)
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")
await alarm.play(**kwargs)
call_spy.assert_called_with("play_alarm", request_params)
with pytest.raises(ValueError, match="Invalid duration"):
await alarm.play(duration=-1)
with pytest.raises(ValueError, match="Invalid sound"):
await alarm.play(sound="unknown")
with pytest.raises(ValueError, match="Invalid volume"):
await alarm.play(volume="unknown") # type: ignore[arg-type]
with pytest.raises(ValueError, match="Invalid volume"):
await alarm.play(volume=-1)
@alarm
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")
await alarm.stop()
call_spy.assert_called_with("stop_alarm")
@alarm
@pytest.mark.parametrize(
("method", "value", "target_key"),
[
pytest.param(
"set_alarm_sound", "Doorbell Ring 1", "type", id="set_alarm_sound"
),
pytest.param("set_alarm_volume", "low", "volume", id="set_alarm_volume"),
pytest.param("set_alarm_duration", 10, "duration", id="set_alarm_duration"),
],
)
async def test_set_alarm_configure(
dev: SmartDevice,
mocker: MockerFixture,
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")
await getattr(alarm, method)(value)
expected_params = {"duration": mocker.ANY, "type": mocker.ANY, "volume": mocker.ANY}
expected_params[target_key] = value
call_spy.assert_called_with("set_alarm_configure", expected_params)