from __future__ import annotations import pytest from pytest_mock import MockerFixture from kasa import Module from kasa.smart import SmartDevice from kasa.smart.modules import Alarm from ...device_fixtures import get_parent_and_child_modules, parametrize alarm = parametrize("has alarm", component_filter="alarm", protocol_filter={"SMART"}) @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): """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): """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, request_params, mocker: MockerFixture): """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): """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, ): """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)