test_lock.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. from unittest.mock import AsyncMock, Mock, patch
  2. import pytest
  3. from switchbot import SwitchbotModel
  4. from switchbot.const.lock import LockStatus
  5. from switchbot.devices import lock
  6. from .test_adv_parser import generate_ble_device
  7. def create_device_for_command_testing(model: str):
  8. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  9. return lock.SwitchbotLock(
  10. ble_device, "ff", "ffffffffffffffffffffffffffffffff", model=model
  11. )
  12. @pytest.mark.parametrize(
  13. "model",
  14. [
  15. SwitchbotModel.LOCK,
  16. SwitchbotModel.LOCK_LITE,
  17. SwitchbotModel.LOCK_PRO,
  18. SwitchbotModel.LOCK_ULTRA,
  19. ],
  20. )
  21. def test_lock_init(model: str):
  22. """Test the initialization of the lock device."""
  23. device = create_device_for_command_testing(model)
  24. assert device._model == model
  25. @pytest.mark.parametrize(
  26. "model",
  27. [
  28. SwitchbotModel.AIR_PURIFIER,
  29. ],
  30. )
  31. def test_lock_init_with_invalid_model(model: str):
  32. """Test that initializing with an invalid model raises ValueError."""
  33. with pytest.raises(
  34. ValueError, match="initializing SwitchbotLock with a non-lock model"
  35. ):
  36. create_device_for_command_testing(model)
  37. @pytest.mark.asyncio
  38. @pytest.mark.parametrize(
  39. "model",
  40. [
  41. SwitchbotModel.LOCK,
  42. SwitchbotModel.LOCK_LITE,
  43. SwitchbotModel.LOCK_PRO,
  44. SwitchbotModel.LOCK_ULTRA,
  45. ],
  46. )
  47. async def test_verify_encryption_key(model: str):
  48. """Test verify_encryption_key method."""
  49. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  50. with patch("switchbot.devices.lock.super") as mock_super:
  51. mock_super().verify_encryption_key = AsyncMock(return_value=True)
  52. result = await lock.SwitchbotLock.verify_encryption_key(
  53. ble_device, "key_id", "encryption_key", model
  54. )
  55. assert result is True
  56. @pytest.mark.asyncio
  57. @pytest.mark.parametrize(
  58. ("model", "command"),
  59. [
  60. (SwitchbotModel.LOCK, b"W\x0fN\x01\x01\x10\x80"),
  61. (SwitchbotModel.LOCK_LITE, b"W\x0fN\x01\x01\x10\x81"),
  62. (SwitchbotModel.LOCK_PRO, b"W\x0fN\x01\x01\x10\x85"),
  63. (SwitchbotModel.LOCK_ULTRA, b"W\x0fN\x01\x01\x10\x86"),
  64. ],
  65. )
  66. async def test_lock(model: str, command: bytes):
  67. """Test lock method."""
  68. device = create_device_for_command_testing(model)
  69. device._get_adv_value = Mock(return_value=LockStatus.UNLOCKED)
  70. with (
  71. patch.object(device, "_send_command", return_value=b"\x01\x00"),
  72. patch.object(device, "_enable_notifications", return_value=True),
  73. patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
  74. ):
  75. result = await device.lock()
  76. assert result is True
  77. @pytest.mark.asyncio
  78. @pytest.mark.parametrize(
  79. ("model", "command"),
  80. [
  81. (SwitchbotModel.LOCK, b"W\x0fN\x01\x01\x10\x80"),
  82. (SwitchbotModel.LOCK_LITE, b"W\x0fN\x01\x01\x10\x81"),
  83. (SwitchbotModel.LOCK_PRO, b"W\x0fN\x01\x01\x10\x84"),
  84. (SwitchbotModel.LOCK_ULTRA, b"W\x0fN\x01\x01\x10\x83"),
  85. ],
  86. )
  87. async def test_unlock(model: str, command: bytes):
  88. """Test unlock method."""
  89. device = create_device_for_command_testing(model)
  90. device._get_adv_value = Mock(return_value=LockStatus.LOCKED)
  91. with (
  92. patch.object(device, "_send_command", return_value=b"\x01\x00"),
  93. patch.object(device, "_enable_notifications", return_value=True),
  94. patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
  95. ):
  96. result = await device.unlock()
  97. assert result is True
  98. @pytest.mark.asyncio
  99. @pytest.mark.parametrize(
  100. "model",
  101. [
  102. SwitchbotModel.LOCK,
  103. SwitchbotModel.LOCK_LITE,
  104. SwitchbotModel.LOCK_PRO,
  105. SwitchbotModel.LOCK_ULTRA,
  106. ],
  107. )
  108. async def test_unlock_without_unlatch(model: str):
  109. """Test unlock_without_unlatch method."""
  110. device = create_device_for_command_testing(model)
  111. device._get_adv_value = Mock(return_value=LockStatus.LOCKED)
  112. with (
  113. patch.object(device, "_send_command", return_value=b"\x01\x00"),
  114. patch.object(device, "_enable_notifications", return_value=True),
  115. patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
  116. ):
  117. result = await device.unlock_without_unlatch()
  118. assert result is True
  119. @pytest.mark.asyncio
  120. @pytest.mark.parametrize(
  121. "model",
  122. [
  123. SwitchbotModel.LOCK,
  124. SwitchbotModel.LOCK_LITE,
  125. SwitchbotModel.LOCK_PRO,
  126. SwitchbotModel.LOCK_ULTRA,
  127. ],
  128. )
  129. async def test_get_basic_info(model: str):
  130. """Test get_basic_info method."""
  131. device = create_device_for_command_testing(model)
  132. lock_data = b"\x00\x80\x00\x00\x00\x00\x00\x00"
  133. basic_data = b"\x00\x64\x01"
  134. with (
  135. patch.object(device, "_get_lock_info", return_value=lock_data),
  136. patch.object(device, "_get_basic_info", return_value=basic_data),
  137. ):
  138. result = await device.get_basic_info()
  139. assert result is not None
  140. assert "battery" in result
  141. assert "firmware" in result
  142. assert "calibration" in result
  143. assert "status" in result
  144. @pytest.mark.asyncio
  145. @pytest.mark.parametrize(
  146. "model",
  147. [
  148. SwitchbotModel.LOCK,
  149. SwitchbotModel.LOCK_LITE,
  150. SwitchbotModel.LOCK_PRO,
  151. SwitchbotModel.LOCK_ULTRA,
  152. ],
  153. )
  154. async def test_get_basic_info_no_lock_data(model: str):
  155. """Test get_basic_info when no lock data is returned."""
  156. device = create_device_for_command_testing(model)
  157. with patch.object(device, "_get_lock_info", return_value=None):
  158. result = await device.get_basic_info()
  159. assert result is None
  160. @pytest.mark.asyncio
  161. @pytest.mark.parametrize(
  162. "model",
  163. [
  164. SwitchbotModel.LOCK,
  165. SwitchbotModel.LOCK_LITE,
  166. SwitchbotModel.LOCK_PRO,
  167. SwitchbotModel.LOCK_ULTRA,
  168. ],
  169. )
  170. async def test_get_basic_info_no_basic_data(model: str):
  171. """Test get_basic_info when no basic data is returned."""
  172. device = create_device_for_command_testing(model)
  173. lock_data = b"\x00\x80\x00\x00\x00\x00\x00\x00"
  174. with (
  175. patch.object(device, "_get_lock_info", return_value=lock_data),
  176. patch.object(device, "_get_basic_info", return_value=None),
  177. ):
  178. result = await device.get_basic_info()
  179. assert result is None
  180. def test_parse_basic_data():
  181. """Test _parse_basic_data method."""
  182. device = create_device_for_command_testing(SwitchbotModel.LOCK)
  183. basic_data = b"\x00\x64\x01"
  184. result = device._parse_basic_data(basic_data)
  185. assert result["battery"] == 100
  186. assert result["firmware"] == 0.1
  187. @pytest.mark.parametrize(
  188. "model",
  189. [
  190. SwitchbotModel.LOCK,
  191. SwitchbotModel.LOCK_LITE,
  192. SwitchbotModel.LOCK_PRO,
  193. SwitchbotModel.LOCK_ULTRA,
  194. ],
  195. )
  196. def test_is_calibrated(model: str):
  197. """Test is_calibrated method."""
  198. device = create_device_for_command_testing(model)
  199. device._get_adv_value = Mock(return_value=True)
  200. assert device.is_calibrated() is True
  201. @pytest.mark.parametrize(
  202. "model",
  203. [
  204. SwitchbotModel.LOCK,
  205. SwitchbotModel.LOCK_LITE,
  206. SwitchbotModel.LOCK_PRO,
  207. SwitchbotModel.LOCK_ULTRA,
  208. ],
  209. )
  210. def test_get_lock_status(model: str):
  211. """Test get_lock_status method."""
  212. device = create_device_for_command_testing(model)
  213. device._get_adv_value = Mock(return_value=LockStatus.LOCKED)
  214. assert device.get_lock_status() == LockStatus.LOCKED
  215. @pytest.mark.parametrize(
  216. "model",
  217. [
  218. SwitchbotModel.LOCK,
  219. SwitchbotModel.LOCK_PRO,
  220. SwitchbotModel.LOCK_ULTRA,
  221. ],
  222. )
  223. def test_is_door_open(model: str):
  224. """Test is_door_open method."""
  225. device = create_device_for_command_testing(model)
  226. device._get_adv_value = Mock(return_value=True)
  227. assert device.is_door_open() is True
  228. @pytest.mark.parametrize(
  229. "model",
  230. [
  231. SwitchbotModel.LOCK,
  232. SwitchbotModel.LOCK_PRO,
  233. SwitchbotModel.LOCK_ULTRA,
  234. ],
  235. )
  236. def test_is_unclosed_alarm_on(model: str):
  237. """Test is_unclosed_alarm_on method."""
  238. device = create_device_for_command_testing(model)
  239. device._get_adv_value = Mock(return_value=True)
  240. assert device.is_unclosed_alarm_on() is True
  241. @pytest.mark.parametrize(
  242. "model",
  243. [
  244. SwitchbotModel.LOCK,
  245. SwitchbotModel.LOCK_LITE,
  246. SwitchbotModel.LOCK_PRO,
  247. SwitchbotModel.LOCK_ULTRA,
  248. ],
  249. )
  250. def test_is_unlocked_alarm_on(model: str):
  251. """Test is_unlocked_alarm_on method."""
  252. device = create_device_for_command_testing(model)
  253. device._get_adv_value = Mock(return_value=True)
  254. assert device.is_unlocked_alarm_on() is True
  255. @pytest.mark.parametrize(
  256. "model",
  257. [
  258. SwitchbotModel.LOCK,
  259. ],
  260. )
  261. def test_is_auto_lock_paused(model: str):
  262. """Test is_auto_lock_paused method."""
  263. device = create_device_for_command_testing(model)
  264. device._get_adv_value = Mock(return_value=True)
  265. assert device.is_auto_lock_paused() is True
  266. @pytest.mark.parametrize(
  267. "model",
  268. [
  269. SwitchbotModel.LOCK,
  270. SwitchbotModel.LOCK_LITE,
  271. SwitchbotModel.LOCK_PRO,
  272. SwitchbotModel.LOCK_ULTRA,
  273. ],
  274. )
  275. def test_is_night_latch_enabled(model: str):
  276. """Test is_night_latch_enabled method."""
  277. device = create_device_for_command_testing(model)
  278. device._get_adv_value = Mock(return_value=True)
  279. assert device.is_night_latch_enabled() is True
  280. @pytest.mark.asyncio
  281. @pytest.mark.parametrize(
  282. "model",
  283. [
  284. SwitchbotModel.LOCK,
  285. SwitchbotModel.LOCK_LITE,
  286. SwitchbotModel.LOCK_PRO,
  287. SwitchbotModel.LOCK_ULTRA,
  288. ],
  289. )
  290. async def test_get_lock_info(model: str):
  291. """Test _get_lock_info method."""
  292. device = create_device_for_command_testing(model)
  293. expected_data = b"\x01\x00\x80\x00\x00\x00\x00\x00"
  294. with patch.object(device, "_send_command", return_value=expected_data):
  295. result = await device._get_lock_info()
  296. assert result == expected_data
  297. @pytest.mark.asyncio
  298. @pytest.mark.parametrize(
  299. "model",
  300. [
  301. SwitchbotModel.LOCK,
  302. SwitchbotModel.LOCK_LITE,
  303. SwitchbotModel.LOCK_PRO,
  304. SwitchbotModel.LOCK_ULTRA,
  305. ],
  306. )
  307. async def test_get_lock_info_failure(model: str):
  308. """Test _get_lock_info method when command fails."""
  309. device = create_device_for_command_testing(model)
  310. with patch.object(device, "_send_command", return_value=b"\x00\x00"):
  311. result = await device._get_lock_info()
  312. assert result is None
  313. @pytest.mark.asyncio
  314. @pytest.mark.parametrize(
  315. "model",
  316. [
  317. SwitchbotModel.LOCK,
  318. SwitchbotModel.LOCK_LITE,
  319. SwitchbotModel.LOCK_PRO,
  320. SwitchbotModel.LOCK_ULTRA,
  321. ],
  322. )
  323. async def test_enable_notifications(model: str):
  324. """Test _enable_notifications method."""
  325. device = create_device_for_command_testing(model)
  326. with patch.object(device, "_send_command", return_value=b"\x01\x00"):
  327. result = await device._enable_notifications()
  328. assert result is True
  329. assert device._notifications_enabled is True
  330. @pytest.mark.asyncio
  331. @pytest.mark.parametrize(
  332. "model",
  333. [
  334. SwitchbotModel.LOCK,
  335. SwitchbotModel.LOCK_LITE,
  336. SwitchbotModel.LOCK_PRO,
  337. SwitchbotModel.LOCK_ULTRA,
  338. ],
  339. )
  340. async def test_enable_notifications_already_enabled(model: str):
  341. """Test _enable_notifications when already enabled."""
  342. device = create_device_for_command_testing(model)
  343. device._notifications_enabled = True
  344. with patch.object(device, "_send_command") as mock_send:
  345. result = await device._enable_notifications()
  346. assert result is True
  347. mock_send.assert_not_called()
  348. @pytest.mark.asyncio
  349. @pytest.mark.parametrize(
  350. "model",
  351. [
  352. SwitchbotModel.LOCK,
  353. SwitchbotModel.LOCK_LITE,
  354. SwitchbotModel.LOCK_PRO,
  355. SwitchbotModel.LOCK_ULTRA,
  356. ],
  357. )
  358. async def test_disable_notifications(model: str):
  359. """Test _disable_notifications method."""
  360. device = create_device_for_command_testing(model)
  361. device._notifications_enabled = True
  362. with patch.object(device, "_send_command", return_value=b"\x01\x00"):
  363. result = await device._disable_notifications()
  364. assert result is True
  365. assert device._notifications_enabled is False
  366. @pytest.mark.asyncio
  367. @pytest.mark.parametrize(
  368. "model",
  369. [
  370. SwitchbotModel.LOCK,
  371. SwitchbotModel.LOCK_LITE,
  372. SwitchbotModel.LOCK_PRO,
  373. SwitchbotModel.LOCK_ULTRA,
  374. ],
  375. )
  376. async def test_disable_notifications_already_disabled(model: str):
  377. """Test _disable_notifications when already disabled."""
  378. device = create_device_for_command_testing(model)
  379. device._notifications_enabled = False
  380. with patch.object(device, "_send_command") as mock_send:
  381. result = await device._disable_notifications()
  382. assert result is True
  383. mock_send.assert_not_called()
  384. @pytest.mark.parametrize(
  385. "model",
  386. [
  387. SwitchbotModel.LOCK,
  388. SwitchbotModel.LOCK_LITE,
  389. SwitchbotModel.LOCK_PRO,
  390. SwitchbotModel.LOCK_ULTRA,
  391. ],
  392. )
  393. def test_notification_handler(model: str):
  394. """Test _notification_handler method."""
  395. device = create_device_for_command_testing(model)
  396. device._notifications_enabled = True
  397. data = bytearray(b"\x0f\x00\x00\x00\x80\x00\x00\x00\x00\x00")
  398. with patch.object(device, "_update_lock_status") as mock_update:
  399. device._notification_handler(0, data)
  400. mock_update.assert_called_once_with(data)
  401. @pytest.mark.parametrize(
  402. "model",
  403. [
  404. SwitchbotModel.LOCK,
  405. SwitchbotModel.LOCK_LITE,
  406. SwitchbotModel.LOCK_PRO,
  407. SwitchbotModel.LOCK_ULTRA,
  408. ],
  409. )
  410. def test_notification_handler_not_enabled(model: str):
  411. """Test _notification_handler when notifications not enabled."""
  412. device = create_device_for_command_testing(model)
  413. device._notifications_enabled = False
  414. data = bytearray(b"\x0f\x00\x00\x00\x80\x00\x00\x00\x00\x00")
  415. with (
  416. patch.object(device, "_update_lock_status") as mock_update,
  417. patch.object(
  418. device.__class__.__bases__[0], "_notification_handler"
  419. ) as mock_super,
  420. ):
  421. device._notification_handler(0, data)
  422. mock_update.assert_not_called()
  423. mock_super.assert_called_once()
  424. @pytest.mark.parametrize(
  425. "model",
  426. [
  427. SwitchbotModel.LOCK,
  428. SwitchbotModel.LOCK_LITE,
  429. SwitchbotModel.LOCK_PRO,
  430. SwitchbotModel.LOCK_ULTRA,
  431. ],
  432. )
  433. def test_update_lock_status(model: str):
  434. """Test _update_lock_status method."""
  435. device = create_device_for_command_testing(model)
  436. data = bytearray(b"\x0f\x00\x00\x00\x80\x00\x00\x00\x00\x00")
  437. with (
  438. patch.object(device, "_decrypt", return_value=b"\x80\x00\x00\x00\x00\x00"),
  439. patch.object(device, "_update_parsed_data", return_value=True),
  440. patch.object(device, "_reset_disconnect_timer"),
  441. patch.object(device, "_fire_callbacks"),
  442. ):
  443. device._update_lock_status(data)
  444. @pytest.mark.parametrize(
  445. ("model", "data", "expected"),
  446. [
  447. (
  448. SwitchbotModel.LOCK,
  449. b"\x80\x00\x00\x00\x00\x00",
  450. {
  451. "calibration": True,
  452. "status": LockStatus.LOCKED, # (0x80 & 0b01110000) >> 4 = 0 = LOCKED
  453. "door_open": False,
  454. "unclosed_alarm": False,
  455. "unlocked_alarm": False,
  456. },
  457. ),
  458. (
  459. SwitchbotModel.LOCK_LITE,
  460. b"\x80\x00\x00\x00\x00\x00",
  461. {
  462. "calibration": True,
  463. "status": LockStatus.LOCKED, # (0x80 & 0b01110000) >> 4 = 0 = LOCKED
  464. "unlocked_alarm": False,
  465. },
  466. ),
  467. (
  468. SwitchbotModel.LOCK_PRO,
  469. b"\x80\x00\x00\x00\x00\x00",
  470. {
  471. "calibration": True,
  472. "status": LockStatus.LOCKED, # (0x80 & 0b01111000) >> 3 = 0 = LOCKED
  473. "door_open": False,
  474. "unclosed_alarm": False,
  475. "unlocked_alarm": False,
  476. },
  477. ),
  478. (
  479. SwitchbotModel.LOCK_ULTRA,
  480. b"\x88\x10\x00\x00\x00\xc0",
  481. {
  482. "calibration": True,
  483. "status": LockStatus.UNLOCKED, # (0x88 & 0b01111000) >> 3 = 0x08 >> 3 = 1 = UNLOCKED
  484. "door_open": True,
  485. "unclosed_alarm": True,
  486. "unlocked_alarm": True,
  487. },
  488. ),
  489. ],
  490. )
  491. def test_parse_lock_data(model: str, data: bytes, expected: dict):
  492. """Test _parse_lock_data static method."""
  493. result = lock.SwitchbotLock._parse_lock_data(data, model)
  494. assert result == expected
  495. @pytest.mark.parametrize(
  496. ("model", "data", "expected"),
  497. [
  498. # Test LOCK with different status bits and flags
  499. (
  500. SwitchbotModel.LOCK,
  501. b"\x94\x00\x00\x00\x00\x00", # Unlocked status (0x10 >> 4 = 1) with door open
  502. {
  503. "calibration": True,
  504. "status": LockStatus.UNLOCKED,
  505. "door_open": True,
  506. "unclosed_alarm": False,
  507. "unlocked_alarm": False,
  508. },
  509. ),
  510. # Test LOCK_LITE without door_open field
  511. (
  512. SwitchbotModel.LOCK_LITE,
  513. b"\x90\x10\x00\x00\x00\x00", # Unlocked with unlocked alarm
  514. {
  515. "calibration": True,
  516. "status": LockStatus.UNLOCKED,
  517. "unlocked_alarm": True,
  518. },
  519. ),
  520. # Test LOCK_PRO with new bit positions
  521. (
  522. SwitchbotModel.LOCK_PRO,
  523. b"\x90\x10\x00\x00\x00\xc0", # New format: status bits 3-6, door open bit 4 of byte 1
  524. {
  525. "calibration": True,
  526. "status": LockStatus.LOCKING, # (0x90 & 0b01111000) >> 3 = 0x10 >> 3 = 2 (LOCKING)
  527. "door_open": True, # bit 4 of byte 1 (0x10)
  528. "unclosed_alarm": True, # bit 7 of byte 5 (0xc0)
  529. "unlocked_alarm": True, # bit 6 of byte 5 (0xc0)
  530. },
  531. ),
  532. # Test LOCK_ULTRA with same format as PRO
  533. (
  534. SwitchbotModel.LOCK_ULTRA,
  535. b"\x88\x00\x00\x00\x00\x40", # Unlocked with unlocked alarm only
  536. {
  537. "calibration": True,
  538. "status": LockStatus.UNLOCKED, # (0x88 & 0b01111000) >> 3 = 0x08 >> 3 = 1
  539. "door_open": False,
  540. "unclosed_alarm": False,
  541. "unlocked_alarm": True, # bit 6 of byte 5
  542. },
  543. ),
  544. ],
  545. )
  546. def test_parse_lock_data_new_formats(model: str, data: bytes, expected: dict):
  547. """Test _parse_lock_data with new format changes."""
  548. result = lock.SwitchbotLock._parse_lock_data(data, model)
  549. assert result == expected
  550. @pytest.mark.asyncio
  551. @pytest.mark.parametrize(
  552. "model",
  553. [
  554. SwitchbotModel.LOCK,
  555. SwitchbotModel.LOCK_LITE,
  556. SwitchbotModel.LOCK_PRO,
  557. SwitchbotModel.LOCK_ULTRA,
  558. ],
  559. )
  560. async def test_lock_with_update(model: str):
  561. """Test lock method with status update."""
  562. device = create_device_for_command_testing(model)
  563. device._get_adv_value = Mock(side_effect=[None, LockStatus.UNLOCKED])
  564. with (
  565. patch.object(device, "update", new_callable=AsyncMock),
  566. patch.object(device, "_send_command", return_value=b"\x01\x00"),
  567. patch.object(device, "_enable_notifications", return_value=True),
  568. patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
  569. ):
  570. result = await device.lock()
  571. assert result is True
  572. @pytest.mark.asyncio
  573. @pytest.mark.parametrize(
  574. ("model", "status"),
  575. [
  576. (SwitchbotModel.LOCK, LockStatus.LOCKED),
  577. (SwitchbotModel.LOCK_LITE, LockStatus.LOCKING),
  578. (SwitchbotModel.LOCK_PRO, LockStatus.LOCKED),
  579. (SwitchbotModel.LOCK_ULTRA, LockStatus.LOCKING),
  580. ],
  581. )
  582. async def test_lock_already_locked(model: str, status: LockStatus):
  583. """Test lock method when already locked."""
  584. device = create_device_for_command_testing(model)
  585. device._get_adv_value = Mock(return_value=status)
  586. with patch.object(device, "_send_command") as mock_send:
  587. result = await device.lock()
  588. assert result is True
  589. mock_send.assert_not_called()
  590. @pytest.mark.asyncio
  591. @pytest.mark.parametrize(
  592. "model",
  593. [
  594. SwitchbotModel.LOCK,
  595. SwitchbotModel.LOCK_LITE,
  596. SwitchbotModel.LOCK_PRO,
  597. SwitchbotModel.LOCK_ULTRA,
  598. ],
  599. )
  600. async def test_lock_with_invalid_basic_data(model: str):
  601. """Test lock method with invalid basic data."""
  602. device = create_device_for_command_testing(model)
  603. device._get_adv_value = Mock(return_value=LockStatus.UNLOCKED)
  604. with (
  605. patch.object(device, "_send_command", return_value=b"\x01\x00"),
  606. patch.object(device, "_enable_notifications", return_value=True),
  607. patch.object(device, "_get_basic_info", return_value=b"\x00"),
  608. ):
  609. result = await device.lock()
  610. assert result is True