mirror of
https://github.com/python-kasa/python-kasa.git
synced 2026-02-27 13:20:05 +00:00
Add preset support for Tapo cameras (#1615)
This commit is contained in:
212
tests/smartcam/modules/test_pantilt.py
Normal file
212
tests/smartcam/modules/test_pantilt.py
Normal file
@@ -0,0 +1,212 @@
|
||||
"""Tests for PanTilt module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Device, Module
|
||||
|
||||
from ...device_fixtures import parametrize
|
||||
|
||||
pantilt = parametrize(
|
||||
"has pantilt", component_filter="ptz", protocol_filter={"SMARTCAM"}
|
||||
)
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_presets(dev: Device, mocker: MockerFixture):
|
||||
"""Test PanTilt module preset functionality."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
presets = pantilt_mod.presets
|
||||
if not presets:
|
||||
pytest.skip("Device has no presets configured")
|
||||
|
||||
assert "ptz_preset" in dev.features
|
||||
preset_feature = dev.features["ptz_preset"]
|
||||
assert preset_feature is not None
|
||||
|
||||
first_preset_name = next(iter(presets.keys()))
|
||||
assert preset_feature.value == first_preset_name
|
||||
|
||||
mock_protocol_query = mocker.patch.object(dev.protocol, "query")
|
||||
mock_protocol_query.return_value = {}
|
||||
|
||||
await preset_feature.set_value(first_preset_name)
|
||||
|
||||
mock_protocol_query.assert_called_once()
|
||||
call_args = mock_protocol_query.call_args
|
||||
assert "motorMoveToPreset" in str(call_args)
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_save_preset(dev: Device, mocker: MockerFixture):
|
||||
"""Test PanTilt save_preset functionality."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
mock_protocol_query = mocker.patch.object(dev.protocol, "query")
|
||||
mock_protocol_query.return_value = {}
|
||||
|
||||
await pantilt_mod.save_preset("NewPreset")
|
||||
|
||||
mock_protocol_query.assert_called_with(
|
||||
request={
|
||||
"addMotorPostion": {
|
||||
"preset": {"set_preset": {"name": "NewPreset", "save_ptz": "1"}}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_invalid_preset(dev: Device, mocker: MockerFixture):
|
||||
"""Test set_preset with invalid preset name raises ValueError."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
if not pantilt_mod.presets:
|
||||
pytest.skip("Device has no presets configured")
|
||||
|
||||
preset_feature = dev.features.get("ptz_preset")
|
||||
if not preset_feature:
|
||||
pytest.skip("Device has no preset feature")
|
||||
|
||||
mocker.patch.object(dev.protocol, "query", return_value={})
|
||||
|
||||
with pytest.raises(ValueError, match="Unexpected value"):
|
||||
await preset_feature.set_value("NonExistentPreset12345")
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_move(dev: Device, mocker: MockerFixture):
|
||||
"""Test PanTilt move commands."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
mock_protocol_query = mocker.patch.object(dev.protocol, "query")
|
||||
mock_protocol_query.return_value = {}
|
||||
|
||||
await pantilt_mod.pan(30)
|
||||
call_args = mock_protocol_query.call_args
|
||||
assert "motor" in str(call_args)
|
||||
assert "move" in str(call_args)
|
||||
|
||||
mock_protocol_query.reset_mock()
|
||||
|
||||
await pantilt_mod.tilt(10)
|
||||
call_args = mock_protocol_query.call_args
|
||||
assert "motor" in str(call_args)
|
||||
assert "move" in str(call_args)
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_goto_preset(dev: Device, mocker: MockerFixture):
|
||||
"""Test PanTilt goto_preset command."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
mock_protocol_query = mocker.patch.object(dev.protocol, "query")
|
||||
mock_protocol_query.return_value = {}
|
||||
|
||||
await pantilt_mod.goto_preset("1")
|
||||
|
||||
mock_protocol_query.assert_called_with(
|
||||
request={"motorMoveToPreset": {"preset": {"goto_preset": {"id": "1"}}}}
|
||||
)
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_get_presets(dev: Device, mocker: MockerFixture):
|
||||
"""Test PanTilt get_presets command."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
mock_protocol_query = mocker.patch.object(dev.protocol, "query")
|
||||
mock_protocol_query.return_value = {}
|
||||
|
||||
await pantilt_mod.get_presets()
|
||||
|
||||
mock_protocol_query.assert_called_with(
|
||||
request={"getPresetConfig": {"preset": {"name": ["preset"]}}}
|
||||
)
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_set_preset_by_id(dev: Device, mocker: MockerFixture):
|
||||
"""Test set_preset with preset ID instead of name."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
if not pantilt_mod.presets:
|
||||
pytest.skip("Device has no presets configured")
|
||||
|
||||
mock_protocol_query = mocker.patch.object(dev.protocol, "query")
|
||||
mock_protocol_query.return_value = {}
|
||||
|
||||
# Get the first preset ID
|
||||
first_preset_id = next(iter(pantilt_mod.presets.values()))
|
||||
|
||||
# Call set_preset with ID instead of name
|
||||
await pantilt_mod.set_preset(first_preset_id)
|
||||
|
||||
mock_protocol_query.assert_called_with(
|
||||
request={
|
||||
"motorMoveToPreset": {"preset": {"goto_preset": {"id": first_preset_id}}}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_set_preset_not_found(dev: Device, mocker: MockerFixture):
|
||||
"""Test set_preset with non-existent preset returns empty dict."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
mock_protocol_query = mocker.patch.object(dev.protocol, "query")
|
||||
mock_protocol_query.return_value = {}
|
||||
|
||||
# Call set_preset with a non-existent preset
|
||||
result = await pantilt_mod.set_preset("NonExistentPreset99999")
|
||||
|
||||
# Should return empty dict and not call API
|
||||
assert result == {}
|
||||
mock_protocol_query.assert_not_called()
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_step_features(dev: Device, mocker: MockerFixture):
|
||||
"""Test pan/tilt step features."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
# Test pan_step feature
|
||||
pan_step_feature = dev.features.get("pan_step")
|
||||
assert pan_step_feature is not None
|
||||
assert pan_step_feature.value == 30 # DEFAULT_PAN_STEP
|
||||
|
||||
await pan_step_feature.set_value(45)
|
||||
assert pantilt_mod._pan_step == 45
|
||||
|
||||
# Test tilt_step feature
|
||||
tilt_step_feature = dev.features.get("tilt_step")
|
||||
assert tilt_step_feature is not None
|
||||
assert tilt_step_feature.value == 10 # DEFAULT_TILT_STEP
|
||||
|
||||
await tilt_step_feature.set_value(20)
|
||||
assert pantilt_mod._tilt_step == 20
|
||||
|
||||
|
||||
@pantilt
|
||||
async def test_pantilt_no_presets_in_data(dev: Device, mocker: MockerFixture):
|
||||
"""Test _presets returns empty dict when no preset data."""
|
||||
pantilt_mod = dev.modules.get(Module.PanTilt)
|
||||
assert pantilt_mod is not None
|
||||
|
||||
# Mock data property to return empty dict (no preset key)
|
||||
mocker.patch.object(type(pantilt_mod), "data", property(lambda self: {}))
|
||||
|
||||
assert pantilt_mod._presets == {}
|
||||
assert pantilt_mod.presets == {}
|
||||
Reference in New Issue
Block a user