123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- from unittest.mock import AsyncMock, Mock, patch
- import pytest
- from switchbot import SwitchbotModel
- from switchbot.const.lock import LockStatus
- from switchbot.devices import lock
- from .test_adv_parser import generate_ble_device
- def create_device_for_command_testing(model: str):
- ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
- return lock.SwitchbotLock(
- ble_device, "ff", "ffffffffffffffffffffffffffffffff", model=model
- )
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_lock_init(model: str):
- """Test the initialization of the lock device."""
- device = create_device_for_command_testing(model)
- assert device._model == model
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.AIR_PURIFIER,
- ],
- )
- def test_lock_init_with_invalid_model(model: str):
- """Test that initializing with an invalid model raises ValueError."""
- with pytest.raises(
- ValueError, match="initializing SwitchbotLock with a non-lock model"
- ):
- create_device_for_command_testing(model)
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_verify_encryption_key(model: str):
- """Test verify_encryption_key method."""
- ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
- with patch("switchbot.devices.lock.super") as mock_super:
- mock_super().verify_encryption_key = AsyncMock(return_value=True)
- result = await lock.SwitchbotLock.verify_encryption_key(
- ble_device, "key_id", "encryption_key", model
- )
- assert result is True
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- ("model", "command"),
- [
- (SwitchbotModel.LOCK, b"W\x0fN\x01\x01\x10\x80"),
- (SwitchbotModel.LOCK_LITE, b"W\x0fN\x01\x01\x10\x81"),
- (SwitchbotModel.LOCK_PRO, b"W\x0fN\x01\x01\x10\x85"),
- (SwitchbotModel.LOCK_ULTRA, b"W\x0fN\x01\x01\x10\x86"),
- ],
- )
- async def test_lock(model: str, command: bytes):
- """Test lock method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=LockStatus.UNLOCKED)
- with (
- patch.object(device, "_send_command", return_value=b"\x01\x00"),
- patch.object(device, "_enable_notifications", return_value=True),
- patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
- ):
- result = await device.lock()
- assert result is True
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- ("model", "command"),
- [
- (SwitchbotModel.LOCK, b"W\x0fN\x01\x01\x10\x80"),
- (SwitchbotModel.LOCK_LITE, b"W\x0fN\x01\x01\x10\x81"),
- (SwitchbotModel.LOCK_PRO, b"W\x0fN\x01\x01\x10\x84"),
- (SwitchbotModel.LOCK_ULTRA, b"W\x0fN\x01\x01\x10\x83"),
- ],
- )
- async def test_unlock(model: str, command: bytes):
- """Test unlock method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=LockStatus.LOCKED)
- with (
- patch.object(device, "_send_command", return_value=b"\x01\x00"),
- patch.object(device, "_enable_notifications", return_value=True),
- patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
- ):
- result = await device.unlock()
- assert result is True
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_unlock_without_unlatch(model: str):
- """Test unlock_without_unlatch method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=LockStatus.LOCKED)
- with (
- patch.object(device, "_send_command", return_value=b"\x01\x00"),
- patch.object(device, "_enable_notifications", return_value=True),
- patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
- ):
- result = await device.unlock_without_unlatch()
- assert result is True
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_get_basic_info(model: str):
- """Test get_basic_info method."""
- device = create_device_for_command_testing(model)
- lock_data = b"\x00\x80\x00\x00\x00\x00\x00\x00"
- basic_data = b"\x00\x64\x01"
- with (
- patch.object(device, "_get_lock_info", return_value=lock_data),
- patch.object(device, "_get_basic_info", return_value=basic_data),
- ):
- result = await device.get_basic_info()
- assert result is not None
- assert "battery" in result
- assert "firmware" in result
- assert "calibration" in result
- assert "status" in result
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_get_basic_info_no_lock_data(model: str):
- """Test get_basic_info when no lock data is returned."""
- device = create_device_for_command_testing(model)
- with patch.object(device, "_get_lock_info", return_value=None):
- result = await device.get_basic_info()
- assert result is None
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_get_basic_info_no_basic_data(model: str):
- """Test get_basic_info when no basic data is returned."""
- device = create_device_for_command_testing(model)
- lock_data = b"\x00\x80\x00\x00\x00\x00\x00\x00"
- with (
- patch.object(device, "_get_lock_info", return_value=lock_data),
- patch.object(device, "_get_basic_info", return_value=None),
- ):
- result = await device.get_basic_info()
- assert result is None
- def test_parse_basic_data():
- """Test _parse_basic_data method."""
- device = create_device_for_command_testing(SwitchbotModel.LOCK)
- basic_data = b"\x00\x64\x01"
- result = device._parse_basic_data(basic_data)
- assert result["battery"] == 100
- assert result["firmware"] == 0.1
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_is_calibrated(model: str):
- """Test is_calibrated method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=True)
- assert device.is_calibrated() is True
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_get_lock_status(model: str):
- """Test get_lock_status method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=LockStatus.LOCKED)
- assert device.get_lock_status() == LockStatus.LOCKED
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_is_door_open(model: str):
- """Test is_door_open method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=True)
- assert device.is_door_open() is True
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_is_unclosed_alarm_on(model: str):
- """Test is_unclosed_alarm_on method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=True)
- assert device.is_unclosed_alarm_on() is True
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_is_unlocked_alarm_on(model: str):
- """Test is_unlocked_alarm_on method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=True)
- assert device.is_unlocked_alarm_on() is True
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- ],
- )
- def test_is_auto_lock_paused(model: str):
- """Test is_auto_lock_paused method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=True)
- assert device.is_auto_lock_paused() is True
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_is_night_latch_enabled(model: str):
- """Test is_night_latch_enabled method."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=True)
- assert device.is_night_latch_enabled() is True
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_get_lock_info(model: str):
- """Test _get_lock_info method."""
- device = create_device_for_command_testing(model)
- expected_data = b"\x01\x00\x80\x00\x00\x00\x00\x00"
- with patch.object(device, "_send_command", return_value=expected_data):
- result = await device._get_lock_info()
- assert result == expected_data
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_get_lock_info_failure(model: str):
- """Test _get_lock_info method when command fails."""
- device = create_device_for_command_testing(model)
- with patch.object(device, "_send_command", return_value=b"\x00\x00"):
- result = await device._get_lock_info()
- assert result is None
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_enable_notifications(model: str):
- """Test _enable_notifications method."""
- device = create_device_for_command_testing(model)
- with patch.object(device, "_send_command", return_value=b"\x01\x00"):
- result = await device._enable_notifications()
- assert result is True
- assert device._notifications_enabled is True
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_enable_notifications_already_enabled(model: str):
- """Test _enable_notifications when already enabled."""
- device = create_device_for_command_testing(model)
- device._notifications_enabled = True
- with patch.object(device, "_send_command") as mock_send:
- result = await device._enable_notifications()
- assert result is True
- mock_send.assert_not_called()
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_disable_notifications(model: str):
- """Test _disable_notifications method."""
- device = create_device_for_command_testing(model)
- device._notifications_enabled = True
- with patch.object(device, "_send_command", return_value=b"\x01\x00"):
- result = await device._disable_notifications()
- assert result is True
- assert device._notifications_enabled is False
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_disable_notifications_already_disabled(model: str):
- """Test _disable_notifications when already disabled."""
- device = create_device_for_command_testing(model)
- device._notifications_enabled = False
- with patch.object(device, "_send_command") as mock_send:
- result = await device._disable_notifications()
- assert result is True
- mock_send.assert_not_called()
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_notification_handler(model: str):
- """Test _notification_handler method."""
- device = create_device_for_command_testing(model)
- device._notifications_enabled = True
- data = bytearray(b"\x0f\x00\x00\x00\x80\x00\x00\x00\x00\x00")
- with patch.object(device, "_update_lock_status") as mock_update:
- device._notification_handler(0, data)
- mock_update.assert_called_once_with(data)
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_notification_handler_not_enabled(model: str):
- """Test _notification_handler when notifications not enabled."""
- device = create_device_for_command_testing(model)
- device._notifications_enabled = False
- data = bytearray(b"\x0f\x00\x00\x00\x80\x00\x00\x00\x00\x00")
- with (
- patch.object(device, "_update_lock_status") as mock_update,
- patch.object(
- device.__class__.__bases__[0], "_notification_handler"
- ) as mock_super,
- ):
- device._notification_handler(0, data)
- mock_update.assert_not_called()
- mock_super.assert_called_once()
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- def test_update_lock_status(model: str):
- """Test _update_lock_status method."""
- device = create_device_for_command_testing(model)
- data = bytearray(b"\x0f\x00\x00\x00\x80\x00\x00\x00\x00\x00")
- with (
- patch.object(device, "_decrypt", return_value=b"\x80\x00\x00\x00\x00\x00"),
- patch.object(device, "_update_parsed_data", return_value=True),
- patch.object(device, "_reset_disconnect_timer"),
- patch.object(device, "_fire_callbacks"),
- ):
- device._update_lock_status(data)
- @pytest.mark.parametrize(
- ("model", "data", "expected"),
- [
- (
- SwitchbotModel.LOCK,
- b"\x80\x00\x00\x00\x00\x00",
- {
- "calibration": True,
- "status": LockStatus.LOCKED,
- "door_open": False,
- "unclosed_alarm": False,
- "unlocked_alarm": False,
- },
- ),
- (
- SwitchbotModel.LOCK_LITE,
- b"\x80\x00\x00\x00\x00\x00",
- {
- "calibration": True,
- "status": LockStatus.LOCKED,
- "unlocked_alarm": False,
- },
- ),
- (
- SwitchbotModel.LOCK_PRO,
- b"\x80\x00\x00\x00\x00\x00",
- {
- "calibration": True,
- "status": LockStatus.LOCKED,
- "door_open": False,
- "unclosed_alarm": False,
- "unlocked_alarm": False,
- },
- ),
- (
- SwitchbotModel.LOCK_ULTRA,
- b"\x88\x10\x00\x00\x00\xc0",
- {
- "calibration": True,
- "status": LockStatus.UNLOCKED,
- "door_open": True,
- "unclosed_alarm": True,
- "unlocked_alarm": True,
- },
- ),
- ],
- )
- def test_parse_lock_data(model: str, data: bytes, expected: dict):
- """Test _parse_lock_data static method."""
- result = lock.SwitchbotLock._parse_lock_data(data, model)
- assert result == expected
- @pytest.mark.parametrize(
- ("model", "data", "expected"),
- [
-
- (
- SwitchbotModel.LOCK,
- b"\x94\x00\x00\x00\x00\x00",
- {
- "calibration": True,
- "status": LockStatus.UNLOCKED,
- "door_open": True,
- "unclosed_alarm": False,
- "unlocked_alarm": False,
- },
- ),
-
- (
- SwitchbotModel.LOCK_LITE,
- b"\x90\x10\x00\x00\x00\x00",
- {
- "calibration": True,
- "status": LockStatus.UNLOCKED,
- "unlocked_alarm": True,
- },
- ),
-
- (
- SwitchbotModel.LOCK_PRO,
- b"\x90\x10\x00\x00\x00\xc0",
- {
- "calibration": True,
- "status": LockStatus.LOCKING,
- "door_open": True,
- "unclosed_alarm": True,
- "unlocked_alarm": True,
- },
- ),
-
- (
- SwitchbotModel.LOCK_ULTRA,
- b"\x88\x00\x00\x00\x00\x40",
- {
- "calibration": True,
- "status": LockStatus.UNLOCKED,
- "door_open": False,
- "unclosed_alarm": False,
- "unlocked_alarm": True,
- },
- ),
- ],
- )
- def test_parse_lock_data_new_formats(model: str, data: bytes, expected: dict):
- """Test _parse_lock_data with new format changes."""
- result = lock.SwitchbotLock._parse_lock_data(data, model)
- assert result == expected
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_lock_with_update(model: str):
- """Test lock method with status update."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(side_effect=[None, LockStatus.UNLOCKED])
- with (
- patch.object(device, "update", new_callable=AsyncMock),
- patch.object(device, "_send_command", return_value=b"\x01\x00"),
- patch.object(device, "_enable_notifications", return_value=True),
- patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
- ):
- result = await device.lock()
- assert result is True
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- ("model", "status"),
- [
- (SwitchbotModel.LOCK, LockStatus.LOCKED),
- (SwitchbotModel.LOCK_LITE, LockStatus.LOCKING),
- (SwitchbotModel.LOCK_PRO, LockStatus.LOCKED),
- (SwitchbotModel.LOCK_ULTRA, LockStatus.LOCKING),
- ],
- )
- async def test_lock_already_locked(model: str, status: LockStatus):
- """Test lock method when already locked."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=status)
- with patch.object(device, "_send_command") as mock_send:
- result = await device.lock()
- assert result is True
- mock_send.assert_not_called()
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "model",
- [
- SwitchbotModel.LOCK,
- SwitchbotModel.LOCK_LITE,
- SwitchbotModel.LOCK_PRO,
- SwitchbotModel.LOCK_ULTRA,
- ],
- )
- async def test_lock_with_invalid_basic_data(model: str):
- """Test lock method with invalid basic data."""
- device = create_device_for_command_testing(model)
- device._get_adv_value = Mock(return_value=LockStatus.UNLOCKED)
- with (
- patch.object(device, "_send_command", return_value=b"\x01\x00"),
- patch.object(device, "_enable_notifications", return_value=True),
- patch.object(device, "_get_basic_info", return_value=b"\x00"),
- ):
- result = await device.lock()
- assert result is True
|